mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Display jump to last topic message bubble.
This commit is contained in:
parent
97356032ac
commit
ede34578da
18 changed files with 486 additions and 320 deletions
|
@ -16,6 +16,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
|
#include "ui/text/text_options.h"
|
||||||
|
#include "ui/text/text_utilities.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "storage/storage_facade.h"
|
#include "storage/storage_facade.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
|
@ -33,9 +35,67 @@ namespace {
|
||||||
constexpr auto kLoadedChatsMinCount = 20;
|
constexpr auto kLoadedChatsMinCount = 20;
|
||||||
constexpr auto kShowChatNamesCount = 8;
|
constexpr auto kShowChatNamesCount = 8;
|
||||||
|
|
||||||
|
[[nodiscard]] TextWithEntities ComposeFolderListEntryText(
|
||||||
|
not_null<Folder*> folder) {
|
||||||
|
const auto &list = folder->lastHistories();
|
||||||
|
if (list.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto count = std::max(
|
||||||
|
int(list.size()),
|
||||||
|
folder->chatsList()->fullSize().current());
|
||||||
|
|
||||||
|
const auto throwAwayLastName = (list.size() > 1)
|
||||||
|
&& (count == list.size() + 1);
|
||||||
|
auto &&peers = ranges::views::all(
|
||||||
|
list
|
||||||
|
) | ranges::views::take(
|
||||||
|
list.size() - (throwAwayLastName ? 1 : 0)
|
||||||
|
);
|
||||||
|
const auto wrapName = [](not_null<History*> history) {
|
||||||
|
const auto name = history->peer->name();
|
||||||
|
return TextWithEntities{
|
||||||
|
.text = name,
|
||||||
|
.entities = (history->chatListBadgesState().unread
|
||||||
|
? EntitiesInText{
|
||||||
|
{ EntityType::Semibold, 0, int(name.size()), QString() },
|
||||||
|
{ EntityType::PlainLink, 0, int(name.size()), QString() },
|
||||||
|
}
|
||||||
|
: EntitiesInText{}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const auto shown = int(peers.size());
|
||||||
|
const auto accumulated = [&] {
|
||||||
|
Expects(shown > 0);
|
||||||
|
|
||||||
|
auto i = peers.begin();
|
||||||
|
auto result = wrapName(*i);
|
||||||
|
for (++i; i != peers.end(); ++i) {
|
||||||
|
result = tr::lng_archived_last_list(
|
||||||
|
tr::now,
|
||||||
|
lt_accumulated,
|
||||||
|
result,
|
||||||
|
lt_chat,
|
||||||
|
wrapName(*i),
|
||||||
|
Ui::Text::WithEntities);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}();
|
||||||
|
return (shown < count)
|
||||||
|
? tr::lng_archived_last(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
(count - shown),
|
||||||
|
lt_chats,
|
||||||
|
accumulated,
|
||||||
|
Ui::Text::WithEntities)
|
||||||
|
: accumulated;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Folder::Folder(not_null<Data::Session*> owner, FolderId id)
|
Folder::Folder(not_null<Session*> owner, FolderId id)
|
||||||
: Entry(owner, Type::Folder)
|
: Entry(owner, Type::Folder)
|
||||||
, _id(id)
|
, _id(id)
|
||||||
, _chatsList(
|
, _chatsList(
|
||||||
|
@ -76,29 +136,6 @@ FolderId Folder::id() const {
|
||||||
|
|
||||||
void Folder::indexNameParts() {
|
void Folder::indexNameParts() {
|
||||||
// We don't want archive to be filtered in the chats list.
|
// We don't want archive to be filtered in the chats list.
|
||||||
//_nameWords.clear();
|
|
||||||
//_nameFirstLetters.clear();
|
|
||||||
//auto toIndexList = QStringList();
|
|
||||||
//auto appendToIndex = [&](const QString &value) {
|
|
||||||
// if (!value.isEmpty()) {
|
|
||||||
// toIndexList.push_back(TextUtilities::RemoveAccents(value));
|
|
||||||
// }
|
|
||||||
//};
|
|
||||||
|
|
||||||
//appendToIndex(_name);
|
|
||||||
//const auto appendTranslit = !toIndexList.isEmpty()
|
|
||||||
// && cRussianLetters().match(toIndexList.front()).hasMatch();
|
|
||||||
//if (appendTranslit) {
|
|
||||||
// appendToIndex(translitRusEng(toIndexList.front()));
|
|
||||||
//}
|
|
||||||
//auto toIndex = toIndexList.join(' ');
|
|
||||||
//toIndex += ' ' + rusKeyboardLayoutSwitch(toIndex);
|
|
||||||
|
|
||||||
//const auto namesList = TextUtilities::PrepareSearchWords(toIndex);
|
|
||||||
//for (const auto &name : namesList) {
|
|
||||||
// _nameWords.insert(name);
|
|
||||||
// _nameFirstLetters.insert(name[0]);
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Folder::registerOne(not_null<History*> history) {
|
void Folder::registerOne(not_null<History*> history) {
|
||||||
|
@ -110,7 +147,6 @@ void Folder::registerOne(not_null<History*> history) {
|
||||||
} else {
|
} else {
|
||||||
updateChatListEntry();
|
updateChatListEntry();
|
||||||
}
|
}
|
||||||
applyChatListMessage(history->chatListMessage());
|
|
||||||
reorderLastHistories();
|
reorderLastHistories();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,9 +154,6 @@ void Folder::unregisterOne(not_null<History*> history) {
|
||||||
if (_chatsList.empty()) {
|
if (_chatsList.empty()) {
|
||||||
updateChatListExistence();
|
updateChatListExistence();
|
||||||
}
|
}
|
||||||
if (_chatListMessage && _chatListMessage->history() == history) {
|
|
||||||
computeChatListMessage();
|
|
||||||
}
|
|
||||||
reorderLastHistories();
|
reorderLastHistories();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,47 +162,11 @@ int Folder::chatListNameVersion() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Folder::oneListMessageChanged(HistoryItem *from, HistoryItem *to) {
|
void Folder::oneListMessageChanged(HistoryItem *from, HistoryItem *to) {
|
||||||
if (!applyChatListMessage(to) && _chatListMessage == from) {
|
|
||||||
computeChatListMessage();
|
|
||||||
}
|
|
||||||
if (from || to) {
|
if (from || to) {
|
||||||
reorderLastHistories();
|
reorderLastHistories();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Folder::applyChatListMessage(HistoryItem *item) {
|
|
||||||
if (!item) {
|
|
||||||
return false;
|
|
||||||
} else if (_chatListMessage
|
|
||||||
&& _chatListMessage->date() >= item->date()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_chatListMessage = item;
|
|
||||||
updateChatListEntry();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Folder::computeChatListMessage() {
|
|
||||||
auto &&items = ranges::views::all(
|
|
||||||
*_chatsList.indexed()
|
|
||||||
) | ranges::views::filter([](not_null<Dialogs::Row*> row) {
|
|
||||||
return row->entry()->chatListMessage() != nullptr;
|
|
||||||
});
|
|
||||||
const auto chatListDate = [](not_null<Dialogs::Row*> row) {
|
|
||||||
return row->entry()->chatListMessage()->date();
|
|
||||||
};
|
|
||||||
const auto top = ranges::max_element(
|
|
||||||
items,
|
|
||||||
ranges::less(),
|
|
||||||
chatListDate);
|
|
||||||
if (top == items.end()) {
|
|
||||||
_chatListMessage = nullptr;
|
|
||||||
} else {
|
|
||||||
_chatListMessage = (*top)->entry()->chatListMessage();
|
|
||||||
}
|
|
||||||
updateChatListEntry();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Folder::reorderLastHistories() {
|
void Folder::reorderLastHistories() {
|
||||||
// We want first kShowChatNamesCount histories, by last message date.
|
// We want first kShowChatNamesCount histories, by last message date.
|
||||||
const auto pred = [](not_null<History*> a, not_null<History*> b) {
|
const auto pred = [](not_null<History*> a, not_null<History*> b) {
|
||||||
|
@ -219,7 +216,7 @@ void Folder::loadUserpic() {
|
||||||
|
|
||||||
void Folder::paintUserpic(
|
void Folder::paintUserpic(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
std::shared_ptr<Data::CloudImageView> &view,
|
std::shared_ptr<CloudImageView> &view,
|
||||||
const Dialogs::Ui::PaintContext &context) const {
|
const Dialogs::Ui::PaintContext &context) const {
|
||||||
paintUserpic(
|
paintUserpic(
|
||||||
p,
|
p,
|
||||||
|
@ -288,8 +285,16 @@ const std::vector<not_null<History*>> &Folder::lastHistories() const {
|
||||||
return _lastHistories;
|
return _lastHistories;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 Folder::chatListViewVersion() const {
|
void Folder::validateListEntryCache() {
|
||||||
return _chatListViewVersion;
|
if (_listEntryCacheVersion == _chatListViewVersion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_listEntryCacheVersion = _chatListViewVersion;
|
||||||
|
_listEntryCache.setMarkedText(
|
||||||
|
st::dialogsTextStyle,
|
||||||
|
ComposeFolderListEntryText(this),
|
||||||
|
// Use rich options as long as the entry text does not have user text.
|
||||||
|
Ui::ItemTextDefaultOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Folder::requestChatListMessage() {
|
void Folder::requestChatListMessage() {
|
||||||
|
@ -299,7 +304,7 @@ void Folder::requestChatListMessage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeId Folder::adjustedChatListTimeId() const {
|
TimeId Folder::adjustedChatListTimeId() const {
|
||||||
return _chatListMessage ? _chatListMessage->date() : chatListTimeId();
|
return chatListTimeId();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Folder::applyDialog(const MTPDdialogFolder &data) {
|
void Folder::applyDialog(const MTPDdialogFolder &data) {
|
||||||
|
@ -350,7 +355,7 @@ Dialogs::BadgesState Folder::chatListBadgesState() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryItem *Folder::chatListMessage() const {
|
HistoryItem *Folder::chatListMessage() const {
|
||||||
return _chatListMessage;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Folder::chatListMessageKnown() const {
|
bool Folder::chatListMessageKnown() const {
|
||||||
|
|
|
@ -69,12 +69,13 @@ public:
|
||||||
const style::color &overrideFg) const;
|
const style::color &overrideFg) const;
|
||||||
|
|
||||||
const std::vector<not_null<History*>> &lastHistories() const;
|
const std::vector<not_null<History*>> &lastHistories() const;
|
||||||
uint32 chatListViewVersion() const;
|
void validateListEntryCache();
|
||||||
|
[[nodiscard]] const Ui::Text::String &listEntryCache() const {
|
||||||
|
return _listEntryCache;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void indexNameParts();
|
void indexNameParts();
|
||||||
bool applyChatListMessage(HistoryItem *item);
|
|
||||||
void computeChatListMessage();
|
|
||||||
|
|
||||||
int chatListNameVersion() const override;
|
int chatListNameVersion() const override;
|
||||||
|
|
||||||
|
@ -96,8 +97,10 @@ private:
|
||||||
base::flat_set<QChar> _nameFirstLetters;
|
base::flat_set<QChar> _nameFirstLetters;
|
||||||
|
|
||||||
std::vector<not_null<History*>> _lastHistories;
|
std::vector<not_null<History*>> _lastHistories;
|
||||||
HistoryItem *_chatListMessage = nullptr;
|
|
||||||
uint32 _chatListViewVersion = 0;
|
Ui::Text::String _listEntryCache;
|
||||||
|
int _listEntryCacheVersion = 0;
|
||||||
|
int _chatListViewVersion = 0;
|
||||||
//rpl::variable<MessagePosition> _unreadPosition;
|
//rpl::variable<MessagePosition> _unreadPosition;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
|
@ -625,6 +625,10 @@ TextWithEntities ForumTopic::titleWithIcon() const {
|
||||||
return ForumTopicIconWithTitle(_iconId, _title);
|
return ForumTopicIconWithTitle(_iconId, _title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ForumTopic::titleVersion() const {
|
||||||
|
return _titleVersion;
|
||||||
|
}
|
||||||
|
|
||||||
void ForumTopic::applyTitle(const QString &title) {
|
void ForumTopic::applyTitle(const QString &title) {
|
||||||
if (_title == title) {
|
if (_title == title) {
|
||||||
return;
|
return;
|
||||||
|
@ -647,6 +651,7 @@ void ForumTopic::applyIconId(DocumentId iconId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_iconId = iconId;
|
_iconId = iconId;
|
||||||
|
++_titleVersion;
|
||||||
_icon = iconId
|
_icon = iconId
|
||||||
? std::make_unique<Ui::Text::LimitedLoopsEmoji>(
|
? std::make_unique<Ui::Text::LimitedLoopsEmoji>(
|
||||||
owner().customEmojiManager().create(
|
owner().customEmojiManager().create(
|
||||||
|
|
|
@ -114,6 +114,7 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] QString title() const;
|
[[nodiscard]] QString title() const;
|
||||||
[[nodiscard]] TextWithEntities titleWithIcon() const;
|
[[nodiscard]] TextWithEntities titleWithIcon() const;
|
||||||
|
[[nodiscard]] int titleVersion() const;
|
||||||
void applyTitle(const QString &title);
|
void applyTitle(const QString &title);
|
||||||
[[nodiscard]] DocumentId iconId() const;
|
[[nodiscard]] DocumentId iconId() const;
|
||||||
void applyIconId(DocumentId iconId);
|
void applyIconId(DocumentId iconId);
|
||||||
|
|
|
@ -18,6 +18,7 @@ DialogRow {
|
||||||
textLeft: pixels;
|
textLeft: pixels;
|
||||||
textTop: pixels;
|
textTop: pixels;
|
||||||
topicsSkip: pixels;
|
topicsSkip: pixels;
|
||||||
|
topicsSkipBig: pixels;
|
||||||
topicsHeight: pixels;
|
topicsHeight: pixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,9 +78,18 @@ forumDialogRow: DialogRow(defaultDialogRow) {
|
||||||
height: 80px;
|
height: 80px;
|
||||||
textTop: 32px;
|
textTop: 32px;
|
||||||
topicsSkip: 8px;
|
topicsSkip: 8px;
|
||||||
topicsHeight: 20px;
|
topicsSkipBig: 14px;
|
||||||
|
topicsHeight: 21px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forumDialogJumpArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFg }};
|
||||||
|
forumDialogJumpArrowOver: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgOver }};
|
||||||
|
forumDialogJumpArrowSkip: 8px;
|
||||||
|
forumDialogJumpArrowLeft: 3px;
|
||||||
|
forumDialogJumpArrowTop: 3px;
|
||||||
|
forumDialogJumpPadding: margins(8px, 3px, 8px, 3px);
|
||||||
|
forumDialogJumpRadius: 11px;
|
||||||
|
|
||||||
dialogsOnlineBadgeStroke: 2px;
|
dialogsOnlineBadgeStroke: 2px;
|
||||||
dialogsOnlineBadgeSize: 10px;
|
dialogsOnlineBadgeSize: 10px;
|
||||||
dialogsOnlineBadgeSkip: point(0px, 2px);
|
dialogsOnlineBadgeSkip: point(0px, 2px);
|
||||||
|
@ -483,7 +493,3 @@ chooseTopicListItem: PeerListItem(defaultPeerListItem) {
|
||||||
chooseTopicList: PeerList(defaultPeerList) {
|
chooseTopicList: PeerList(defaultPeerList) {
|
||||||
item: chooseTopicListItem;
|
item: chooseTopicListItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
dialogsTopicArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgService }};
|
|
||||||
dialogsTopicArrowSkip: 13px;
|
|
||||||
dialogsTopicArrowTop: 4px;
|
|
||||||
|
|
|
@ -514,22 +514,43 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
||||||
Window::GifPauseReason::Any);
|
Window::GifPauseReason::Any);
|
||||||
auto fullWidth = width();
|
auto fullWidth = width();
|
||||||
auto dialogsClip = r;
|
auto dialogsClip = r;
|
||||||
auto ms = crl::now();
|
const auto ms = crl::now();
|
||||||
|
const auto paintRow = [&](
|
||||||
|
not_null<Row*> row,
|
||||||
|
bool selected,
|
||||||
|
bool mayBeActive) {
|
||||||
|
const auto key = row->key();
|
||||||
|
const auto active = mayBeActive && (activeEntry.key == key);
|
||||||
|
const auto forum = key.history()
|
||||||
|
&& key.history()->peer->isForum();
|
||||||
|
if (forum && !_topicJumpCache) {
|
||||||
|
_topicJumpCache = std::make_unique<Ui::TopicJumpCache>();
|
||||||
|
}
|
||||||
|
Ui::RowPainter::Paint(p, row, validateVideoUserpic(row), {
|
||||||
|
.st = (forum ? &st::forumDialogRow : _st.get()),
|
||||||
|
.topicJumpCache = _topicJumpCache.get(),
|
||||||
|
.folder = _openedFolder,
|
||||||
|
.forum = _openedForum,
|
||||||
|
.filter = _filterId,
|
||||||
|
.now = ms,
|
||||||
|
.width = fullWidth,
|
||||||
|
.active = active,
|
||||||
|
.selected = (_menuRow.key
|
||||||
|
? (row->key() == _menuRow.key)
|
||||||
|
: selected),
|
||||||
|
.paused = videoPaused,
|
||||||
|
.narrow = (fullWidth < st::columnMinimalWidthLeft),
|
||||||
|
});
|
||||||
|
};
|
||||||
if (_state == WidgetState::Default) {
|
if (_state == WidgetState::Default) {
|
||||||
paintCollapsedRows(p, r);
|
paintCollapsedRows(p, r);
|
||||||
|
|
||||||
const auto &list = _shownList->all();
|
const auto &list = _shownList->all();
|
||||||
const auto shownBottom = _shownList->height() - skipTopHeight();
|
const auto shownBottom = _shownList->height() - skipTopHeight();
|
||||||
const auto active = activeEntry.key;
|
const auto active = activeEntry.key;
|
||||||
const auto selected = _menuRow.key
|
const auto selected = isPressed()
|
||||||
? _menuRow.key
|
? (_pressed ? _pressed->key() : Key())
|
||||||
: (isPressed()
|
: (_selected ? _selected->key() : Key());
|
||||||
? (_pressed
|
|
||||||
? _pressed->key()
|
|
||||||
: Key())
|
|
||||||
: (_selected
|
|
||||||
? _selected->key()
|
|
||||||
: Key()));
|
|
||||||
if (shownBottom) {
|
if (shownBottom) {
|
||||||
const auto skip = dialogsOffset();
|
const auto skip = dialogsOffset();
|
||||||
const auto promoted = fixedOnTopCount();
|
const auto promoted = fixedOnTopCount();
|
||||||
|
@ -559,23 +580,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
||||||
if (xadd || yadd) {
|
if (xadd || yadd) {
|
||||||
p.translate(xadd, yadd);
|
p.translate(xadd, yadd);
|
||||||
}
|
}
|
||||||
const auto key = row->key();
|
paintRow(row, (row->key() == selected), true);
|
||||||
const auto isActive = (key == active);
|
|
||||||
const auto isSelected = (key == selected);
|
|
||||||
const auto isForum = key.history()
|
|
||||||
&& key.history()->peer->isForum();
|
|
||||||
Ui::RowPainter::Paint(p, row, validateVideoUserpic(row), {
|
|
||||||
.st = (isForum ? &st::forumDialogRow : _st.get()),
|
|
||||||
.folder = _openedFolder,
|
|
||||||
.forum = _openedForum,
|
|
||||||
.filter = _filterId,
|
|
||||||
.now = ms,
|
|
||||||
.width = fullWidth,
|
|
||||||
.active = isActive,
|
|
||||||
.selected = isSelected,
|
|
||||||
.paused = videoPaused,
|
|
||||||
.narrow = (fullWidth < st::columnMinimalWidthLeft),
|
|
||||||
});
|
|
||||||
if (xadd || yadd) {
|
if (xadd || yadd) {
|
||||||
p.translate(-xadd, -yadd);
|
p.translate(-xadd, -yadd);
|
||||||
}
|
}
|
||||||
|
@ -677,29 +682,11 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
||||||
int(_filterResults.size()));
|
int(_filterResults.size()));
|
||||||
p.translate(0, filteredHeight(from));
|
p.translate(0, filteredHeight(from));
|
||||||
for (; from < to; ++from) {
|
for (; from < to; ++from) {
|
||||||
|
const auto selected = isPressed()
|
||||||
|
? (from == _filteredPressed)
|
||||||
|
: (from == _filteredSelected);
|
||||||
const auto row = _filterResults[from].row;
|
const auto row = _filterResults[from].row;
|
||||||
const auto key = row->key();
|
paintRow(row, selected, !activeEntry.fullId);
|
||||||
const auto active = (activeEntry.key == key)
|
|
||||||
&& !activeEntry.fullId;
|
|
||||||
const auto selected = _menuRow.key
|
|
||||||
? (key == _menuRow.key)
|
|
||||||
: (from == (isPressed()
|
|
||||||
? _filteredPressed
|
|
||||||
: _filteredSelected));
|
|
||||||
const auto isForum = key.history()
|
|
||||||
&& key.history()->peer->isForum();
|
|
||||||
Ui::RowPainter::Paint(p, row, validateVideoUserpic(row), {
|
|
||||||
.st = (isForum ? &st::forumDialogRow : _st.get()),
|
|
||||||
.folder = _openedFolder,
|
|
||||||
.forum = _openedForum,
|
|
||||||
.filter = _filterId,
|
|
||||||
.now = ms,
|
|
||||||
.width = fullWidth,
|
|
||||||
.active = active,
|
|
||||||
.selected = selected,
|
|
||||||
.paused = videoPaused,
|
|
||||||
.narrow = (fullWidth < st::columnMinimalWidthLeft),
|
|
||||||
});
|
|
||||||
p.translate(0, row->height());
|
p.translate(0, row->height());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ namespace Dialogs::Ui {
|
||||||
using namespace ::Ui;
|
using namespace ::Ui;
|
||||||
class VideoUserpic;
|
class VideoUserpic;
|
||||||
struct PaintContext;
|
struct PaintContext;
|
||||||
|
struct TopicJumpCache;
|
||||||
} // namespace Dialogs::Ui
|
} // namespace Dialogs::Ui
|
||||||
|
|
||||||
namespace Dialogs {
|
namespace Dialogs {
|
||||||
|
@ -397,6 +398,7 @@ private:
|
||||||
|
|
||||||
std::vector<std::unique_ptr<CollapsedRow>> _collapsedRows;
|
std::vector<std::unique_ptr<CollapsedRow>> _collapsedRows;
|
||||||
not_null<const style::DialogRow*> _st;
|
not_null<const style::DialogRow*> _st;
|
||||||
|
mutable std::unique_ptr<Ui::TopicJumpCache> _topicJumpCache;
|
||||||
int _collapsedSelected = -1;
|
int _collapsedSelected = -1;
|
||||||
int _collapsedPressed = -1;
|
int _collapsedPressed = -1;
|
||||||
bool _skipTopDialog = false;
|
bool _skipTopDialog = false;
|
||||||
|
|
|
@ -26,67 +26,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "styles/style_dialogs.h"
|
#include "styles/style_dialogs.h"
|
||||||
|
|
||||||
namespace Dialogs {
|
namespace Dialogs {
|
||||||
namespace {
|
|
||||||
|
|
||||||
[[nodiscard]] TextWithEntities ComposeFolderListEntryText(
|
|
||||||
not_null<Data::Folder*> folder) {
|
|
||||||
const auto &list = folder->lastHistories();
|
|
||||||
if (list.empty()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto count = std::max(
|
|
||||||
int(list.size()),
|
|
||||||
folder->chatsList()->fullSize().current());
|
|
||||||
|
|
||||||
const auto throwAwayLastName = (list.size() > 1)
|
|
||||||
&& (count == list.size() + 1);
|
|
||||||
auto &&peers = ranges::views::all(
|
|
||||||
list
|
|
||||||
) | ranges::views::take(
|
|
||||||
list.size() - (throwAwayLastName ? 1 : 0)
|
|
||||||
);
|
|
||||||
const auto wrapName = [](not_null<History*> history) {
|
|
||||||
const auto name = history->peer->name();
|
|
||||||
return TextWithEntities{
|
|
||||||
.text = name,
|
|
||||||
.entities = (history->chatListBadgesState().unread
|
|
||||||
? EntitiesInText{
|
|
||||||
{ EntityType::Semibold, 0, int(name.size()), QString() },
|
|
||||||
{ EntityType::PlainLink, 0, int(name.size()), QString() },
|
|
||||||
}
|
|
||||||
: EntitiesInText{}),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const auto shown = int(peers.size());
|
|
||||||
const auto accumulated = [&] {
|
|
||||||
Expects(shown > 0);
|
|
||||||
|
|
||||||
auto i = peers.begin();
|
|
||||||
auto result = wrapName(*i);
|
|
||||||
for (++i; i != peers.end(); ++i) {
|
|
||||||
result = tr::lng_archived_last_list(
|
|
||||||
tr::now,
|
|
||||||
lt_accumulated,
|
|
||||||
result,
|
|
||||||
lt_chat,
|
|
||||||
wrapName(*i),
|
|
||||||
Ui::Text::WithEntities);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}();
|
|
||||||
return (shown < count)
|
|
||||||
? tr::lng_archived_last(
|
|
||||||
tr::now,
|
|
||||||
lt_count,
|
|
||||||
(count - shown),
|
|
||||||
lt_chats,
|
|
||||||
accumulated,
|
|
||||||
Ui::Text::WithEntities)
|
|
||||||
: accumulated;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
BasicRow::BasicRow() = default;
|
BasicRow::BasicRow() = default;
|
||||||
BasicRow::~BasicRow() = default;
|
BasicRow::~BasicRow() = default;
|
||||||
|
@ -160,31 +99,13 @@ uint64 Row::sortKey(FilterId filterId) const {
|
||||||
return _id.entry()->sortKeyInChatList(filterId);
|
return _id.entry()->sortKeyInChatList(filterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Row::validateListEntryCache() const {
|
|
||||||
const auto folder = _id.folder();
|
|
||||||
if (!folder) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto version = folder->chatListViewVersion();
|
|
||||||
if (_listEntryCacheVersion == version) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_listEntryCacheVersion = version;
|
|
||||||
_listEntryCache.setMarkedText(
|
|
||||||
st::dialogsTextStyle,
|
|
||||||
ComposeFolderListEntryText(folder),
|
|
||||||
// Use rich options as long as the entry text does not have user text.
|
|
||||||
Ui::ItemTextDefaultOptions());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Row::setCornerBadgeShown(
|
void Row::setCornerBadgeShown(
|
||||||
bool shown,
|
bool shown,
|
||||||
Fn<void()> updateCallback) const {
|
Fn<void()> updateCallback) const {
|
||||||
const auto value = shown ? 1 : 0;
|
if (_cornerBadgeShown == shown) {
|
||||||
if (_cornerBadgeShown == value) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_cornerBadgeShown = value;
|
const_cast<Row*>(this)->_cornerBadgeShown = shown;
|
||||||
if (_cornerBadgeUserpic && _cornerBadgeUserpic->animation.animating()) {
|
if (_cornerBadgeUserpic && _cornerBadgeUserpic->animation.animating()) {
|
||||||
_cornerBadgeUserpic->animation.change(
|
_cornerBadgeUserpic->animation.change(
|
||||||
_cornerBadgeShown ? 1. : 0.,
|
_cornerBadgeShown ? 1. : 0.,
|
||||||
|
|
|
@ -119,11 +119,6 @@ public:
|
||||||
}
|
}
|
||||||
[[nodiscard]] uint64 sortKey(FilterId filterId) const;
|
[[nodiscard]] uint64 sortKey(FilterId filterId) const;
|
||||||
|
|
||||||
void validateListEntryCache() const;
|
|
||||||
[[nodiscard]] const Ui::Text::String &listEntryCache() const {
|
|
||||||
return _listEntryCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
// for any attached data, for example View in contacts list
|
// for any attached data, for example View in contacts list
|
||||||
void *attached = nullptr;
|
void *attached = nullptr;
|
||||||
|
|
||||||
|
@ -151,13 +146,11 @@ private:
|
||||||
const Ui::PaintContext &context);
|
const Ui::PaintContext &context);
|
||||||
|
|
||||||
Key _id;
|
Key _id;
|
||||||
mutable Ui::Text::String _listEntryCache;
|
|
||||||
mutable std::unique_ptr<CornerBadgeUserpic> _cornerBadgeUserpic;
|
mutable std::unique_ptr<CornerBadgeUserpic> _cornerBadgeUserpic;
|
||||||
int _top = 0;
|
int _top = 0;
|
||||||
int _height = 0;
|
int _height = 0;
|
||||||
int _index = 0;
|
int _index = 0;
|
||||||
mutable int _listEntryCacheVersion : 31 = 0;
|
bool _cornerBadgeShown = false;
|
||||||
mutable int _cornerBadgeShown : 1 = 0;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -198,22 +198,22 @@ int PaintWideCounter(
|
||||||
return availableWidth - used;
|
return availableWidth - used;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PaintListEntryText(
|
void PaintFolderEntryText(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
not_null<const Row*> row,
|
not_null<Data::Folder*> folder,
|
||||||
const PaintContext &context,
|
const PaintContext &context,
|
||||||
QRect rect) {
|
QRect rect) {
|
||||||
if (rect.isEmpty()) {
|
if (rect.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
row->validateListEntryCache();
|
folder->validateListEntryCache();
|
||||||
p.setFont(st::dialogsTextFont);
|
p.setFont(st::dialogsTextFont);
|
||||||
p.setPen(context.active
|
p.setPen(context.active
|
||||||
? st::dialogsTextFgActive
|
? st::dialogsTextFgActive
|
||||||
: context.selected
|
: context.selected
|
||||||
? st::dialogsTextFgOver
|
? st::dialogsTextFgOver
|
||||||
: st::dialogsTextFg);
|
: st::dialogsTextFg);
|
||||||
row->listEntryCache().draw(p, {
|
folder->listEntryCache().draw(p, {
|
||||||
.position = rect.topLeft(),
|
.position = rect.topLeft(),
|
||||||
.availableWidth = rect.width(),
|
.availableWidth = rect.width(),
|
||||||
.palette = &(context.active
|
.palette = &(context.active
|
||||||
|
@ -342,7 +342,14 @@ void PaintRow(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto texttop = context.st->textTop;
|
auto texttop = context.st->textTop;
|
||||||
if (promoted && !history->topPromotionMessage().isEmpty()) {
|
if (const auto folder = entry->asFolder()) {
|
||||||
|
const auto rect = QRect(
|
||||||
|
nameleft,
|
||||||
|
texttop,
|
||||||
|
namewidth,
|
||||||
|
st::dialogsTextFont->height);
|
||||||
|
PaintFolderEntryText(p, folder, context, rect);
|
||||||
|
} else if (promoted && !history->topPromotionMessage().isEmpty()) {
|
||||||
auto availableWidth = namewidth;
|
auto availableWidth = namewidth;
|
||||||
p.setFont(st::dialogsTextFont);
|
p.setFont(st::dialogsTextFont);
|
||||||
if (history->cloudDraftTextCache().isEmpty()) {
|
if (history->cloudDraftTextCache().isEmpty()) {
|
||||||
|
@ -911,21 +918,19 @@ void RowPainter::Paint(
|
||||||
: thread
|
: thread
|
||||||
? &thread->lastItemDialogsView()
|
? &thread->lastItemDialogsView()
|
||||||
: nullptr;
|
: nullptr;
|
||||||
if (const auto folder = row->folder()) {
|
if (view) {
|
||||||
PaintListEntryText(p, row, context, rect);
|
const auto forum = context.st->topicsHeight
|
||||||
} else if (view) {
|
? row->history()->peer->forum()
|
||||||
if (!view->prepared(item)) {
|
: nullptr;
|
||||||
|
if (!view->prepared(item, forum)) {
|
||||||
view->prepare(
|
view->prepare(
|
||||||
item,
|
item,
|
||||||
|
forum,
|
||||||
[=] { entry->updateChatListEntry(); },
|
[=] { entry->updateChatListEntry(); },
|
||||||
{ .ignoreTopic = (!history || !peer->isForum()) });
|
{});
|
||||||
}
|
}
|
||||||
if (const auto topics = context.st->topicsHeight) {
|
if (forum) {
|
||||||
view->prepareTopics(
|
rect.setHeight(context.st->topicsHeight + rect.height());
|
||||||
row->history()->peer->forum(),
|
|
||||||
rect,
|
|
||||||
[=] { entry->updateChatListEntry(); });
|
|
||||||
rect.setHeight(topics + rect.height());
|
|
||||||
}
|
}
|
||||||
view->paint(p, rect, context);
|
view->paint(p, rect, context);
|
||||||
}
|
}
|
||||||
|
@ -1015,10 +1020,10 @@ void RowPainter::Paint(
|
||||||
availableWidth,
|
availableWidth,
|
||||||
st::dialogsTextFont->height);
|
st::dialogsTextFont->height);
|
||||||
auto &view = row->itemView();
|
auto &view = row->itemView();
|
||||||
if (!view.prepared(item)) {
|
if (!view.prepared(item, nullptr)) {
|
||||||
view.prepare(item, row->repaint(), previewOptions);
|
view.prepare(item, nullptr, row->repaint(), previewOptions);
|
||||||
}
|
}
|
||||||
row->itemView().paint(p, itemRect, context);
|
view.paint(p, itemRect, context);
|
||||||
};
|
};
|
||||||
const auto showSavedMessages = history
|
const auto showSavedMessages = history
|
||||||
&& history->peer->isSelf()
|
&& history->peer->isSelf()
|
||||||
|
|
|
@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/cached_round_corners.h"
|
||||||
|
|
||||||
namespace style {
|
namespace style {
|
||||||
struct DialogRow;
|
struct DialogRow;
|
||||||
} // namespace style
|
} // namespace style
|
||||||
|
@ -35,8 +37,23 @@ using namespace ::Ui;
|
||||||
|
|
||||||
class VideoUserpic;
|
class VideoUserpic;
|
||||||
|
|
||||||
|
struct TopicJumpCorners {
|
||||||
|
Ui::CornersPixmaps normal;
|
||||||
|
Ui::CornersPixmaps inverted;
|
||||||
|
QPixmap small;
|
||||||
|
int invertedRadius = 0;
|
||||||
|
int smallKey = 0; // = `-radius` if top right else `radius`.
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TopicJumpCache {
|
||||||
|
TopicJumpCorners corners;
|
||||||
|
TopicJumpCorners over;
|
||||||
|
TopicJumpCorners rippleMask;
|
||||||
|
};
|
||||||
|
|
||||||
struct PaintContext {
|
struct PaintContext {
|
||||||
not_null<const style::DialogRow*> st;
|
not_null<const style::DialogRow*> st;
|
||||||
|
TopicJumpCache *topicJumpCache = nullptr;
|
||||||
Data::Folder *folder = nullptr;
|
Data::Folder *folder = nullptr;
|
||||||
Data::Forum *forum = nullptr;
|
Data::Forum *forum = nullptr;
|
||||||
FilterId filter = 0;
|
FilterId filter = 0;
|
||||||
|
|
|
@ -127,15 +127,32 @@ bool MessageView::dependsOn(not_null<const HistoryItem*> item) const {
|
||||||
return (_textCachedFor == item.get());
|
return (_textCachedFor == item.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MessageView::prepared(not_null<const HistoryItem*> item) const {
|
bool MessageView::prepared(
|
||||||
return (_textCachedFor == item.get());
|
not_null<const HistoryItem*> item,
|
||||||
|
Data::Forum *forum) const {
|
||||||
|
return (_textCachedFor == item.get())
|
||||||
|
&& (!forum
|
||||||
|
|| (_topics
|
||||||
|
&& _topics->forum() == forum
|
||||||
|
&& _topics->prepared()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageView::prepare(
|
void MessageView::prepare(
|
||||||
not_null<const HistoryItem*> item,
|
not_null<const HistoryItem*> item,
|
||||||
|
Data::Forum *forum,
|
||||||
Fn<void()> customEmojiRepaint,
|
Fn<void()> customEmojiRepaint,
|
||||||
ToPreviewOptions options) {
|
ToPreviewOptions options) {
|
||||||
const auto validateTopics = !options.ignoreTopic;
|
if (!forum) {
|
||||||
|
_topics = nullptr;
|
||||||
|
} else if (!_topics || _topics->forum() != forum) {
|
||||||
|
_topics = std::make_unique<TopicsView>(forum);
|
||||||
|
_topics->prepare(item->topicRootId(), customEmojiRepaint);
|
||||||
|
} else if (!_topics->prepared()) {
|
||||||
|
_topics->prepare(item->topicRootId(), customEmojiRepaint);
|
||||||
|
}
|
||||||
|
if (_textCachedFor == item.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
options.existing = &_imagesCache;
|
options.existing = &_imagesCache;
|
||||||
options.ignoreTopic = true;
|
options.ignoreTopic = true;
|
||||||
auto preview = item->toPreview(options);
|
auto preview = item->toPreview(options);
|
||||||
|
@ -183,17 +200,21 @@ void MessageView::prepare(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageView::prepareTopics(
|
int MessageView::countWidth() const {
|
||||||
not_null<Data::Forum*> forum,
|
auto result = 0;
|
||||||
const QRect &geometry,
|
if (!_senderCache.isEmpty()) {
|
||||||
Fn<void()> customEmojiRepaint) {
|
result += _senderCache.maxWidth();
|
||||||
if (!_topics || _topics->forum() != forum) {
|
if (!_imagesCache.empty()) {
|
||||||
_topics = std::make_unique<TopicsView>(forum);
|
result += st::dialogsMiniPreviewSkip
|
||||||
|
+ st::dialogsMiniPreviewRight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_topics->prepare(
|
if (!_imagesCache.empty()) {
|
||||||
geometry,
|
result += (_imagesCache.size()
|
||||||
&st::forumDialogRow,
|
* (st::dialogsMiniPreview + st::dialogsMiniPreviewSkip))
|
||||||
std::move(customEmojiRepaint));
|
+ st::dialogsMiniPreviewRight;
|
||||||
|
}
|
||||||
|
return result + _textCache.maxWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageView::paint(
|
void MessageView::paint(
|
||||||
|
@ -223,11 +244,22 @@ void MessageView::paint(
|
||||||
: st::dialogsTextPalette));
|
: st::dialogsTextPalette));
|
||||||
|
|
||||||
auto rect = geometry;
|
auto rect = geometry;
|
||||||
|
const auto checkJump = withTopic && !context.active;
|
||||||
|
const auto jump1 = checkJump ? _topics->jumpToTopicWidth() : 0;
|
||||||
|
if (jump1) {
|
||||||
|
paintJumpToLast(p, rect, context, jump1);
|
||||||
|
}
|
||||||
|
|
||||||
if (withTopic) {
|
if (withTopic) {
|
||||||
_topics->paint(p, rect, context);
|
_topics->paint(p, rect, context);
|
||||||
rect.setTop(rect.top() + context.st->topicsHeight);
|
rect.setTop(rect.top() + context.st->topicsHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto finalRight = rect.x() + rect.width();
|
||||||
|
if (jump1) {
|
||||||
|
rect.setWidth(rect.width() - st::forumDialogJumpArrowSkip);
|
||||||
|
finalRight -= st::forumDialogJumpArrowSkip;
|
||||||
|
}
|
||||||
const auto lines = rect.height() / st::dialogsTextFont->height;
|
const auto lines = rect.height() / st::dialogsTextFont->height;
|
||||||
if (!_senderCache.isEmpty()) {
|
if (!_senderCache.isEmpty()) {
|
||||||
_senderCache.draw(p, {
|
_senderCache.draw(p, {
|
||||||
|
@ -258,20 +290,143 @@ void MessageView::paint(
|
||||||
if (!_imagesCache.empty()) {
|
if (!_imagesCache.empty()) {
|
||||||
rect.setLeft(rect.x() + st::dialogsMiniPreviewRight);
|
rect.setLeft(rect.x() + st::dialogsMiniPreviewRight);
|
||||||
}
|
}
|
||||||
if (rect.isEmpty()) {
|
if (!rect.isEmpty()) {
|
||||||
|
_textCache.draw(p, {
|
||||||
|
.position = rect.topLeft(),
|
||||||
|
.availableWidth = rect.width(),
|
||||||
|
.palette = palette,
|
||||||
|
.spoiler = Text::DefaultSpoilerCache(),
|
||||||
|
.now = context.now,
|
||||||
|
.paused = context.paused,
|
||||||
|
.elisionLines = lines,
|
||||||
|
});
|
||||||
|
rect.setLeft(rect.x() + _textCache.maxWidth());
|
||||||
|
}
|
||||||
|
if (jump1) {
|
||||||
|
const auto x = (rect.width() > st::forumDialogJumpArrowSkip)
|
||||||
|
? rect.x()
|
||||||
|
: finalRight;
|
||||||
|
const auto add = st::forumDialogJumpArrowLeft;
|
||||||
|
const auto y = rect.y() + st::forumDialogJumpArrowTop;
|
||||||
|
(context.selected
|
||||||
|
? st::forumDialogJumpArrowOver
|
||||||
|
: st::forumDialogJumpArrow).paint(p, x + add, y, context.width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageView::paintJumpToLast(
|
||||||
|
Painter &p,
|
||||||
|
const QRect &rect,
|
||||||
|
const PaintContext &context,
|
||||||
|
int width1) const {
|
||||||
|
if (!context.topicJumpCache) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_textCache.draw(p, {
|
FillJumpToLastBg(p, {
|
||||||
.position = rect.topLeft(),
|
.st = context.st,
|
||||||
.availableWidth = rect.width(),
|
.corners = (context.selected
|
||||||
.palette = palette,
|
? &context.topicJumpCache->over
|
||||||
.spoiler = Text::DefaultSpoilerCache(),
|
: &context.topicJumpCache->corners),
|
||||||
.now = context.now,
|
.geometry = rect,
|
||||||
.paused = context.paused,
|
.bg = (context.selected
|
||||||
.elisionLines = lines,
|
? st::dialogsRippleBg
|
||||||
|
: st::dialogsBgOver),
|
||||||
|
.width1 = width1,
|
||||||
|
.width2 = countWidth() + st::forumDialogJumpArrowSkip,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FillJumpToLastBg(QPainter &p, JumpToLastBg context) {
|
||||||
|
const auto availableWidth = context.geometry.width();
|
||||||
|
const auto use1 = std::min(context.width1, availableWidth);
|
||||||
|
const auto use2 = std::min(context.width2, availableWidth);
|
||||||
|
const auto padding = st::forumDialogJumpPadding;
|
||||||
|
const auto radius = st::forumDialogJumpRadius;
|
||||||
|
const auto &bg = context.bg;
|
||||||
|
auto &normal = context.corners->normal;
|
||||||
|
auto &inverted = context.corners->inverted;
|
||||||
|
auto &small = context.corners->small;
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
p.setBrush(bg);
|
||||||
|
const auto origin = context.geometry.topLeft();
|
||||||
|
const auto delta = std::abs(use1 - use2);
|
||||||
|
if (delta <= context.st->topicsSkip / 2) {
|
||||||
|
if (normal.p[0].isNull()) {
|
||||||
|
normal = Ui::PrepareCornerPixmaps(radius, bg);
|
||||||
|
}
|
||||||
|
const auto w = std::max(use1, use2);
|
||||||
|
const auto h = context.st->topicsHeight + st::normalFont->height;
|
||||||
|
const auto fill = QRect(origin, QSize(w, h));
|
||||||
|
Ui::FillRoundRect(p, fill.marginsAdded(padding), bg, normal);
|
||||||
|
} else {
|
||||||
|
const auto h1 = context.st->topicsHeight;
|
||||||
|
const auto h2 = st::normalFont->height;
|
||||||
|
const auto hmin = std::min(h1, h2);
|
||||||
|
const auto wantedInvertedRadius = hmin - radius;
|
||||||
|
const auto invertedr = std::min(wantedInvertedRadius, delta / 2);
|
||||||
|
const auto smallr = std::min(radius, delta - invertedr);
|
||||||
|
const auto smallkey = (use1 < use2) ? smallr : (-smallr);
|
||||||
|
if (normal.p[0].isNull()) {
|
||||||
|
normal = Ui::PrepareCornerPixmaps(radius, bg);
|
||||||
|
}
|
||||||
|
if (inverted.p[0].isNull()
|
||||||
|
|| context.corners->invertedRadius != invertedr) {
|
||||||
|
context.corners->invertedRadius = invertedr;
|
||||||
|
inverted = Ui::PrepareInvertedCornerPixmaps(invertedr, bg);
|
||||||
|
}
|
||||||
|
if (smallr != radius
|
||||||
|
&& (small.isNull() || context.corners->smallKey != smallkey)) {
|
||||||
|
context.corners->smallKey = smallr;
|
||||||
|
auto pixmaps = Ui::PrepareCornerPixmaps(smallr, bg);
|
||||||
|
small = pixmaps.p[(use1 < use2) ? 1 : 3];
|
||||||
|
}
|
||||||
|
const auto rect1 = QRect(origin, QSize(use1, h1));
|
||||||
|
auto no1 = normal;
|
||||||
|
no1.p[2] = QPixmap();
|
||||||
|
if (use1 < use2) {
|
||||||
|
no1.p[3] = QPixmap();
|
||||||
|
} else if (smallr != radius) {
|
||||||
|
no1.p[3] = small;
|
||||||
|
}
|
||||||
|
auto fill1 = rect1.marginsAdded({
|
||||||
|
padding.left(),
|
||||||
|
padding.top(),
|
||||||
|
padding.right(),
|
||||||
|
(use1 < use2 ? -padding.top() : padding.bottom()),
|
||||||
|
});
|
||||||
|
Ui::FillRoundRect(p, fill1, bg, no1);
|
||||||
|
if (use1 < use2) {
|
||||||
|
p.drawPixmap(
|
||||||
|
fill1.x() + fill1.width(),
|
||||||
|
fill1.y() + fill1.height() - invertedr,
|
||||||
|
inverted.p[3]);
|
||||||
|
}
|
||||||
|
const auto add = QPoint(0, h1);
|
||||||
|
const auto rect2 = QRect(origin + add, QSize(use2, h2));
|
||||||
|
const auto fill2 = rect2.marginsAdded({
|
||||||
|
padding.left(),
|
||||||
|
(use2 < use1 ? -padding.bottom() : padding.top()),
|
||||||
|
padding.right(),
|
||||||
|
padding.bottom(),
|
||||||
|
});
|
||||||
|
auto no2 = normal;
|
||||||
|
no2.p[0] = QPixmap();
|
||||||
|
if (use2 < use1) {
|
||||||
|
no2.p[1] = QPixmap();
|
||||||
|
} else if (smallr != radius) {
|
||||||
|
no2.p[1] = small;
|
||||||
|
}
|
||||||
|
Ui::FillRoundRect(p, fill2, bg, no2);
|
||||||
|
if (use2 < use1) {
|
||||||
|
p.drawPixmap(
|
||||||
|
fill2.x() + fill2.width(),
|
||||||
|
fill2.y(),
|
||||||
|
inverted.p[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HistoryView::ItemPreview PreviewWithSender(
|
HistoryView::ItemPreview PreviewWithSender(
|
||||||
HistoryView::ItemPreview &&preview,
|
HistoryView::ItemPreview &&preview,
|
||||||
const QString &sender,
|
const QString &sender,
|
||||||
|
|
|
@ -13,6 +13,10 @@ class Image;
|
||||||
class HistoryItem;
|
class HistoryItem;
|
||||||
enum class ImageRoundRadius;
|
enum class ImageRoundRadius;
|
||||||
|
|
||||||
|
namespace style {
|
||||||
|
struct DialogRow;
|
||||||
|
} // namespace style
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
|
@ -32,6 +36,7 @@ using namespace ::Ui;
|
||||||
|
|
||||||
struct PaintContext;
|
struct PaintContext;
|
||||||
class TopicsView;
|
class TopicsView;
|
||||||
|
struct TopicJumpCorners;
|
||||||
|
|
||||||
[[nodiscard]] TextWithEntities DialogsPreviewText(TextWithEntities text);
|
[[nodiscard]] TextWithEntities DialogsPreviewText(TextWithEntities text);
|
||||||
|
|
||||||
|
@ -47,17 +52,15 @@ public:
|
||||||
void itemInvalidated(not_null<const HistoryItem*> item);
|
void itemInvalidated(not_null<const HistoryItem*> item);
|
||||||
[[nodiscard]] bool dependsOn(not_null<const HistoryItem*> item) const;
|
[[nodiscard]] bool dependsOn(not_null<const HistoryItem*> item) const;
|
||||||
|
|
||||||
[[nodiscard]] bool prepared(not_null<const HistoryItem*> item) const;
|
[[nodiscard]] bool prepared(
|
||||||
|
not_null<const HistoryItem*> item,
|
||||||
|
Data::Forum *forum) const;
|
||||||
void prepare(
|
void prepare(
|
||||||
not_null<const HistoryItem*> item,
|
not_null<const HistoryItem*> item,
|
||||||
|
Data::Forum *forum,
|
||||||
Fn<void()> customEmojiRepaint,
|
Fn<void()> customEmojiRepaint,
|
||||||
ToPreviewOptions options);
|
ToPreviewOptions options);
|
||||||
|
|
||||||
void prepareTopics(
|
|
||||||
not_null<Data::Forum*> forum,
|
|
||||||
const QRect &geometry,
|
|
||||||
Fn<void()> customEmojiRepaint);
|
|
||||||
|
|
||||||
void paint(
|
void paint(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
const QRect &geometry,
|
const QRect &geometry,
|
||||||
|
@ -66,6 +69,13 @@ public:
|
||||||
private:
|
private:
|
||||||
struct LoadingContext;
|
struct LoadingContext;
|
||||||
|
|
||||||
|
[[nodiscard]] int countWidth() const;
|
||||||
|
void paintJumpToLast(
|
||||||
|
Painter &p,
|
||||||
|
const QRect &rect,
|
||||||
|
const PaintContext &context,
|
||||||
|
int width1) const;
|
||||||
|
|
||||||
mutable const HistoryItem *_textCachedFor = nullptr;
|
mutable const HistoryItem *_textCachedFor = nullptr;
|
||||||
mutable Text::String _senderCache;
|
mutable Text::String _senderCache;
|
||||||
mutable std::unique_ptr<TopicsView> _topics;
|
mutable std::unique_ptr<TopicsView> _topics;
|
||||||
|
@ -80,4 +90,14 @@ private:
|
||||||
const QString &sender,
|
const QString &sender,
|
||||||
TextWithEntities topic);
|
TextWithEntities topic);
|
||||||
|
|
||||||
|
struct JumpToLastBg {
|
||||||
|
not_null<const style::DialogRow*> st;
|
||||||
|
not_null<TopicJumpCorners*> corners;
|
||||||
|
QRect geometry;
|
||||||
|
const style::color &bg;
|
||||||
|
int width1 = 0;
|
||||||
|
int width2 = 0;
|
||||||
|
};
|
||||||
|
void FillJumpToLastBg(QPainter &p, JumpToLastBg context);
|
||||||
|
|
||||||
} // namespace Dialogs::Ui
|
} // namespace Dialogs::Ui
|
||||||
|
|
|
@ -29,47 +29,76 @@ TopicsView::TopicsView(not_null<Data::Forum*> forum)
|
||||||
|
|
||||||
TopicsView::~TopicsView() = default;
|
TopicsView::~TopicsView() = default;
|
||||||
|
|
||||||
void TopicsView::prepare(
|
bool TopicsView::prepared() const {
|
||||||
const QRect &geometry,
|
return (_version == _forum->recentTopicsListVersion());
|
||||||
not_null<const style::DialogRow*> st,
|
}
|
||||||
Fn<void()> customEmojiRepaint) {
|
|
||||||
|
void TopicsView::prepare(MsgId frontRootId, Fn<void()> customEmojiRepaint) {
|
||||||
|
const auto &list = _forum->recentTopics();
|
||||||
|
_version = _forum->recentTopicsListVersion();
|
||||||
|
_titles.reserve(list.size());
|
||||||
auto index = 0;
|
auto index = 0;
|
||||||
auto available = geometry.width();
|
for (const auto &topic : list) {
|
||||||
for (const auto &topic : _forum->recentTopics()) {
|
const auto from = begin(_titles) + index;
|
||||||
if (available <= 0) {
|
const auto rootId = topic->rootId();
|
||||||
break;
|
const auto i = ranges::find(
|
||||||
} else if (_titles.size() == index) {
|
from,
|
||||||
|
end(_titles),
|
||||||
|
rootId,
|
||||||
|
&Title::topicRootId);
|
||||||
|
if (i != end(_titles)) {
|
||||||
|
if (i != from) {
|
||||||
|
ranges::rotate(from, i, i + 1);
|
||||||
|
}
|
||||||
|
} else if (index >= _titles.size()) {
|
||||||
_titles.emplace_back();
|
_titles.emplace_back();
|
||||||
}
|
}
|
||||||
auto &title = _titles[index];
|
auto &title = _titles[index++];
|
||||||
const auto rootId = topic->rootId();
|
title.topicRootId = rootId;
|
||||||
|
|
||||||
const auto unread = topic->chatListBadgesState().unread;
|
const auto unread = topic->chatListBadgesState().unread;
|
||||||
if (title.topicRootId != rootId || title.unread != unread) {
|
if (title.unread == unread
|
||||||
const auto context = Core::MarkedTextContext{
|
&& title.version == topic->titleVersion()) {
|
||||||
.session = &topic->session(),
|
continue;
|
||||||
.customEmojiRepaint = customEmojiRepaint,
|
|
||||||
.customEmojiLoopLimit = kIconLoopCount,
|
|
||||||
};
|
|
||||||
auto topicTitle = topic->titleWithIcon();
|
|
||||||
title.title.setMarkedText(
|
|
||||||
st::dialogsTextStyle,
|
|
||||||
(unread
|
|
||||||
? Ui::Text::PlainLink(
|
|
||||||
Ui::Text::Wrapped(
|
|
||||||
std::move(topicTitle),
|
|
||||||
EntityType::Bold))
|
|
||||||
: std::move(topicTitle)),
|
|
||||||
DialogTextOptions(),
|
|
||||||
context);
|
|
||||||
title.topicRootId = rootId;
|
|
||||||
title.unread = unread;
|
|
||||||
}
|
}
|
||||||
available -= title.title.maxWidth() + st->topicsSkip;
|
const auto context = Core::MarkedTextContext{
|
||||||
++index;
|
.session = &topic->session(),
|
||||||
|
.customEmojiRepaint = customEmojiRepaint,
|
||||||
|
.customEmojiLoopLimit = kIconLoopCount,
|
||||||
|
};
|
||||||
|
auto topicTitle = topic->titleWithIcon();
|
||||||
|
title.version = topic->titleVersion();
|
||||||
|
title.unread = unread;
|
||||||
|
title.title.setMarkedText(
|
||||||
|
st::dialogsTextStyle,
|
||||||
|
(unread
|
||||||
|
? Ui::Text::PlainLink(
|
||||||
|
Ui::Text::Wrapped(
|
||||||
|
std::move(topicTitle),
|
||||||
|
EntityType::Bold))
|
||||||
|
: std::move(topicTitle)),
|
||||||
|
DialogTextOptions(),
|
||||||
|
context);
|
||||||
}
|
}
|
||||||
while (_titles.size() > index) {
|
while (_titles.size() > index) {
|
||||||
_titles.pop_back();
|
_titles.pop_back();
|
||||||
}
|
}
|
||||||
|
const auto i = frontRootId
|
||||||
|
? ranges::find(_titles, frontRootId, &Title::topicRootId)
|
||||||
|
: end(_titles);
|
||||||
|
_jumpToTopic = (i != end(_titles));
|
||||||
|
if (_jumpToTopic) {
|
||||||
|
if (i != begin(_titles)) {
|
||||||
|
ranges::rotate(begin(_titles), i, i + 1);
|
||||||
|
}
|
||||||
|
if (!_titles.front().unread) {
|
||||||
|
_jumpToTopic = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TopicsView::jumpToTopicWidth() const {
|
||||||
|
return _jumpToTopic ? _titles.front().title.maxWidth() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TopicsView::paint(
|
void TopicsView::paint(
|
||||||
|
@ -91,6 +120,7 @@ void TopicsView::paint(
|
||||||
: st::dialogsTextPaletteArchive);
|
: st::dialogsTextPaletteArchive);
|
||||||
auto index = 0;
|
auto index = 0;
|
||||||
auto rect = geometry;
|
auto rect = geometry;
|
||||||
|
auto skipBig = _jumpToTopic && !context.active;
|
||||||
for (const auto &title : _titles) {
|
for (const auto &title : _titles) {
|
||||||
if (rect.width() <= 0) {
|
if (rect.width() <= 0) {
|
||||||
break;
|
break;
|
||||||
|
@ -104,8 +134,11 @@ void TopicsView::paint(
|
||||||
.paused = context.paused,
|
.paused = context.paused,
|
||||||
.elisionLines = 1,
|
.elisionLines = 1,
|
||||||
});
|
});
|
||||||
rect.setLeft(
|
const auto skip = skipBig
|
||||||
rect.left() + title.title.maxWidth() + context.st->topicsSkip);
|
? context.st->topicsSkipBig
|
||||||
|
: context.st->topicsSkip;
|
||||||
|
rect.setLeft(rect.left() + title.title.maxWidth() + skip);
|
||||||
|
skipBig = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,10 +36,10 @@ public:
|
||||||
return _forum;
|
return _forum;
|
||||||
}
|
}
|
||||||
|
|
||||||
void prepare(
|
[[nodiscard]] bool prepared() const;
|
||||||
const QRect &geometry,
|
void prepare(MsgId frontRootId, Fn<void()> customEmojiRepaint);
|
||||||
not_null<const style::DialogRow*> st,
|
|
||||||
Fn<void()> customEmojiRepaint);
|
[[nodiscard]] int jumpToTopicWidth() const;
|
||||||
|
|
||||||
void paint(
|
void paint(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
|
@ -54,11 +54,14 @@ private:
|
||||||
struct Title {
|
struct Title {
|
||||||
Text::String title;
|
Text::String title;
|
||||||
MsgId topicRootId = 0;
|
MsgId topicRootId = 0;
|
||||||
|
int version = -1;
|
||||||
bool unread = false;
|
bool unread = false;
|
||||||
};
|
};
|
||||||
const not_null<Data::Forum*> _forum;
|
const not_null<Data::Forum*> _forum;
|
||||||
|
|
||||||
mutable std::vector<Title> _titles;
|
mutable std::vector<Title> _titles;
|
||||||
|
int _version = -1;
|
||||||
|
bool _jumpToTopic = false;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
|
|
@ -220,7 +220,7 @@ const CornersPixmaps &CachedCornerPixmaps(CachedRoundCorners index) {
|
||||||
return Corners[index];
|
return Corners[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
CornersPixmaps PrepareCornerPixmaps(int32 radius, style::color bg, const style::color *sh) {
|
CornersPixmaps PrepareCornerPixmaps(int radius, style::color bg, const style::color *sh) {
|
||||||
auto images = PrepareCorners(radius, bg, sh);
|
auto images = PrepareCorners(radius, bg, sh);
|
||||||
auto result = CornersPixmaps();
|
auto result = CornersPixmaps();
|
||||||
for (int j = 0; j < 4; ++j) {
|
for (int j = 0; j < 4; ++j) {
|
||||||
|
@ -240,6 +240,24 @@ CornersPixmaps PrepareCornerPixmaps(ImageRoundRadius radius, style::color bg, co
|
||||||
Unexpected("Image round radius in PrepareCornerPixmaps.");
|
Unexpected("Image round radius in PrepareCornerPixmaps.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CornersPixmaps PrepareInvertedCornerPixmaps(int radius, style::color bg) {
|
||||||
|
const auto size = radius * style::DevicePixelRatio();
|
||||||
|
auto circle = style::colorizeImage(
|
||||||
|
style::createInvertedCircleMask(radius * 2),
|
||||||
|
bg);
|
||||||
|
circle.setDevicePixelRatio(style::DevicePixelRatio());
|
||||||
|
auto result = CornersPixmaps();
|
||||||
|
const auto fill = [&](int index, int xoffset, int yoffset) {
|
||||||
|
result.p[index] = PixmapFromImage(
|
||||||
|
circle.copy(QRect(xoffset, yoffset, size, size)));
|
||||||
|
};
|
||||||
|
fill(0, 0, 0);
|
||||||
|
fill(1, size, 0);
|
||||||
|
fill(2, size, size);
|
||||||
|
fill(3, 0, size);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] int CachedCornerRadiusValue(CachedCornerRadius tag) {
|
[[nodiscard]] int CachedCornerRadiusValue(CachedCornerRadius tag) {
|
||||||
using Radius = CachedCornerRadius;
|
using Radius = CachedCornerRadius;
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
|
|
|
@ -36,25 +36,28 @@ enum CachedRoundCorners : int {
|
||||||
RoundCornersCount
|
RoundCornersCount
|
||||||
};
|
};
|
||||||
|
|
||||||
void FillRoundRect(QPainter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, CachedRoundCorners index);
|
void FillRoundRect(QPainter &p, int x, int y, int w, int h, style::color bg, CachedRoundCorners index);
|
||||||
inline void FillRoundRect(QPainter &p, const QRect &rect, style::color bg, CachedRoundCorners index) {
|
inline void FillRoundRect(QPainter &p, const QRect &rect, style::color bg, CachedRoundCorners index) {
|
||||||
FillRoundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, index);
|
FillRoundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] const CornersPixmaps &CachedCornerPixmaps(CachedRoundCorners index);
|
[[nodiscard]] const CornersPixmaps &CachedCornerPixmaps(CachedRoundCorners index);
|
||||||
[[nodiscard]] CornersPixmaps PrepareCornerPixmaps(
|
[[nodiscard]] CornersPixmaps PrepareCornerPixmaps(
|
||||||
int32 radius,
|
int radius,
|
||||||
style::color bg,
|
style::color bg,
|
||||||
const style::color *sh = nullptr);
|
const style::color *sh = nullptr);
|
||||||
[[nodiscard]] CornersPixmaps PrepareCornerPixmaps(
|
[[nodiscard]] CornersPixmaps PrepareCornerPixmaps(
|
||||||
ImageRoundRadius radius,
|
ImageRoundRadius radius,
|
||||||
style::color bg,
|
style::color bg,
|
||||||
const style::color *sh = nullptr);
|
const style::color *sh = nullptr);
|
||||||
void FillRoundRect(QPainter &p, int32 x, int32 y, int32 w, int32 h, style::color bg, const CornersPixmaps &corners);
|
[[nodiscard]] CornersPixmaps PrepareInvertedCornerPixmaps(
|
||||||
|
int radius,
|
||||||
|
style::color bg);
|
||||||
|
void FillRoundRect(QPainter &p, int x, int y, int w, int h, style::color bg, const CornersPixmaps &corners);
|
||||||
inline void FillRoundRect(QPainter &p, const QRect &rect, style::color bg, const CornersPixmaps &corners) {
|
inline void FillRoundRect(QPainter &p, const QRect &rect, style::color bg, const CornersPixmaps &corners) {
|
||||||
return FillRoundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, corners);
|
return FillRoundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, corners);
|
||||||
}
|
}
|
||||||
void FillRoundShadow(QPainter &p, int32 x, int32 y, int32 w, int32 h, style::color shadow, const CornersPixmaps &corners);
|
void FillRoundShadow(QPainter &p, int x, int y, int w, int h, style::color shadow, const CornersPixmaps &corners);
|
||||||
inline void FillRoundShadow(QPainter &p, const QRect &rect, style::color shadow, const CornersPixmaps &corners) {
|
inline void FillRoundShadow(QPainter &p, const QRect &rect, style::color shadow, const CornersPixmaps &corners) {
|
||||||
FillRoundShadow(p, rect.x(), rect.y(), rect.width(), rect.height(), shadow, corners);
|
FillRoundShadow(p, rect.x(), rect.y(), rect.width(), rect.height(), shadow, corners);
|
||||||
}
|
}
|
||||||
|
|
|
@ -480,20 +480,9 @@ const CornersPixmaps &ChatStyle::serviceBgCornersNormal() const {
|
||||||
|
|
||||||
const CornersPixmaps &ChatStyle::serviceBgCornersInverted() const {
|
const CornersPixmaps &ChatStyle::serviceBgCornersInverted() const {
|
||||||
if (_serviceBgCornersInverted.p[0].isNull()) {
|
if (_serviceBgCornersInverted.p[0].isNull()) {
|
||||||
const auto radius = HistoryServiceMsgInvertedRadius();
|
_serviceBgCornersInverted = Ui::PrepareInvertedCornerPixmaps(
|
||||||
const auto size = radius * style::DevicePixelRatio();
|
HistoryServiceMsgInvertedRadius(),
|
||||||
auto circle = style::colorizeImage(
|
|
||||||
style::createInvertedCircleMask(radius * 2),
|
|
||||||
msgServiceBg());
|
msgServiceBg());
|
||||||
circle.setDevicePixelRatio(style::DevicePixelRatio());
|
|
||||||
const auto fill = [&](int index, int xoffset, int yoffset) {
|
|
||||||
_serviceBgCornersInverted.p[index] = PixmapFromImage(
|
|
||||||
circle.copy(QRect(xoffset, yoffset, size, size)));
|
|
||||||
};
|
|
||||||
fill(0, 0, 0);
|
|
||||||
fill(1, size, 0);
|
|
||||||
fill(2, size, size);
|
|
||||||
fill(3, 0, size);
|
|
||||||
}
|
}
|
||||||
return _serviceBgCornersInverted;
|
return _serviceBgCornersInverted;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue