mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Allow filtering Saved Messages search by tags.
This commit is contained in:
parent
e667436a98
commit
9c151ca151
10 changed files with 518 additions and 36 deletions
|
@ -609,6 +609,8 @@ PRIVATE
|
||||||
dialogs/dialogs_row.h
|
dialogs/dialogs_row.h
|
||||||
dialogs/dialogs_search_from_controllers.cpp
|
dialogs/dialogs_search_from_controllers.cpp
|
||||||
dialogs/dialogs_search_from_controllers.h
|
dialogs/dialogs_search_from_controllers.h
|
||||||
|
dialogs/dialogs_search_tags.cpp
|
||||||
|
dialogs/dialogs_search_tags.h
|
||||||
dialogs/dialogs_widget.cpp
|
dialogs/dialogs_widget.cpp
|
||||||
dialogs/dialogs_widget.h
|
dialogs/dialogs_widget.h
|
||||||
dialogs/ui/dialogs_layout.cpp
|
dialogs/ui/dialogs_layout.cpp
|
||||||
|
|
|
@ -623,3 +623,6 @@ dialogsStoriesTooltipHide: IconButton(defaultIconButton) {
|
||||||
searchedBarHeight: 32px;
|
searchedBarHeight: 32px;
|
||||||
searchedBarFont: normalFont;
|
searchedBarFont: normalFont;
|
||||||
searchedBarPosition: point(17px, 7px);
|
searchedBarPosition: point(17px, 7px);
|
||||||
|
|
||||||
|
dialogsSearchTagSkip: point(8px, 4px);
|
||||||
|
dialogsSearchTagBottom: 10px;
|
||||||
|
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "dialogs/dialogs_indexed_list.h"
|
#include "dialogs/dialogs_indexed_list.h"
|
||||||
#include "dialogs/dialogs_widget.h"
|
#include "dialogs/dialogs_widget.h"
|
||||||
#include "dialogs/dialogs_search_from_controllers.h"
|
#include "dialogs/dialogs_search_from_controllers.h"
|
||||||
|
#include "dialogs/dialogs_search_tags.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "core/shortcuts.h"
|
#include "core/shortcuts.h"
|
||||||
|
@ -40,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_chat_filters.h"
|
#include "data/data_chat_filters.h"
|
||||||
#include "data/data_cloud_file.h"
|
#include "data/data_cloud_file.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
|
#include "data/data_message_reactions.h"
|
||||||
#include "data/data_saved_messages.h"
|
#include "data/data_saved_messages.h"
|
||||||
#include "data/data_stories.h"
|
#include "data/data_stories.h"
|
||||||
#include "data/stickers/data_stickers.h"
|
#include "data/stickers/data_stickers.h"
|
||||||
|
@ -477,18 +479,26 @@ int InnerWidget::peerSearchOffset() const {
|
||||||
+ st::searchedBarHeight;
|
+ st::searchedBarHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
int InnerWidget::searchedOffset() const {
|
int InnerWidget::searchInChatOffset() const {
|
||||||
auto result = peerSearchOffset();
|
auto result = peerSearchOffset() - st::searchedBarHeight;
|
||||||
if (!_peerSearchResults.empty()) {
|
if (!_peerSearchResults.empty()) {
|
||||||
result += (_peerSearchResults.size() * st::dialogsRowHeight)
|
result += (_peerSearchResults.size() * st::dialogsRowHeight)
|
||||||
+ st::searchedBarHeight;
|
+ st::searchedBarHeight;
|
||||||
}
|
}
|
||||||
result += searchInChatSkip();
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int InnerWidget::searchedOffset() const {
|
||||||
|
return searchInChatOffset()
|
||||||
|
+ searchInChatSkip()
|
||||||
|
+ st::searchedBarHeight;
|
||||||
|
}
|
||||||
|
|
||||||
int InnerWidget::searchInChatSkip() const {
|
int InnerWidget::searchInChatSkip() const {
|
||||||
auto result = 0;
|
auto result = 0;
|
||||||
|
if (_searchTags) {
|
||||||
|
result += _searchTags->height();
|
||||||
|
}
|
||||||
if (_searchInChat) {
|
if (_searchInChat) {
|
||||||
result += st::searchedBarHeight + st::dialogsSearchInHeight;
|
result += st::searchedBarHeight + st::dialogsSearchInHeight;
|
||||||
}
|
}
|
||||||
|
@ -1111,12 +1121,20 @@ void InnerWidget::paintSearchInChat(
|
||||||
auto height = searchInChatSkip();
|
auto height = searchInChatSkip();
|
||||||
|
|
||||||
auto top = 0;
|
auto top = 0;
|
||||||
|
if (_searchTags) {
|
||||||
|
const auto height = _searchTags->height();
|
||||||
|
p.fillRect(0, top, width(), height, currentBg());
|
||||||
|
const auto position = QPoint(_searchTagsLeft, 0);
|
||||||
|
_searchTags->paint(p, position, context.now, context.paused);
|
||||||
|
top += height;
|
||||||
|
}
|
||||||
p.setFont(st::searchedBarFont);
|
p.setFont(st::searchedBarFont);
|
||||||
if (_searchInChat) {
|
if (_searchInChat) {
|
||||||
top += st::searchedBarHeight;
|
const auto bar = st::searchedBarHeight;
|
||||||
p.fillRect(0, 0, width(), top, st::searchedBarBg);
|
p.fillRect(0, top, width(), top + bar, st::searchedBarBg);
|
||||||
p.setPen(st::searchedBarFg);
|
p.setPen(st::searchedBarFg);
|
||||||
p.drawTextLeft(st::searchedBarPosition.x(), st::searchedBarPosition.y(), width(), tr::lng_dlg_search_in(tr::now));
|
p.drawTextLeft(st::searchedBarPosition.x(), top + st::searchedBarPosition.y(), width(), tr::lng_dlg_search_in(tr::now));
|
||||||
|
top += bar;
|
||||||
}
|
}
|
||||||
auto fullRect = QRect(0, top, width(), height - top);
|
auto fullRect = QRect(0, top, width(), height - top);
|
||||||
p.fillRect(fullRect, currentBg());
|
p.fillRect(fullRect, currentBg());
|
||||||
|
@ -1276,6 +1294,21 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
|
||||||
_lastMousePosition = globalPosition;
|
_lastMousePosition = globalPosition;
|
||||||
_lastRowLocalMouseX = local.x();
|
_lastRowLocalMouseX = local.x();
|
||||||
|
|
||||||
|
const auto tagBase = QPoint(_searchTagsLeft, searchInChatOffset());
|
||||||
|
const auto tagPoint = local - tagBase;
|
||||||
|
const auto inTags = _searchTags
|
||||||
|
&& QRect(
|
||||||
|
tagBase,
|
||||||
|
QSize(width() - 2 * _searchTagsLeft, _searchTags->height())
|
||||||
|
).contains(local);
|
||||||
|
const auto tagLink = inTags
|
||||||
|
? _searchTags->lookupHandler(tagPoint)
|
||||||
|
: nullptr;
|
||||||
|
ClickHandler::setActive(tagLink);
|
||||||
|
if (inTags) {
|
||||||
|
setCursor(tagLink ? style::cur_pointer : style::cur_default);
|
||||||
|
}
|
||||||
|
|
||||||
const auto w = width();
|
const auto w = width();
|
||||||
const auto mouseY = local.y();
|
const auto mouseY = local.y();
|
||||||
clearIrrelevantState();
|
clearIrrelevantState();
|
||||||
|
@ -1370,7 +1403,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
|
||||||
updateSelectedRow();
|
updateSelectedRow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (wasSelected != isSelected()) {
|
if (!inTags && wasSelected != isSelected()) {
|
||||||
setCursor(wasSelected ? style::cur_default : style::cur_pointer);
|
setCursor(wasSelected ? style::cur_default : style::cur_pointer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1452,6 +1485,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
|
||||||
QSize(width(), _st->height),
|
QSize(width(), _st->height),
|
||||||
row->repaint());
|
row->repaint());
|
||||||
}
|
}
|
||||||
|
ClickHandler::pressed();
|
||||||
if (anim::Disabled()
|
if (anim::Disabled()
|
||||||
&& (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) {
|
&& (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) {
|
||||||
mousePressReleased(e->globalPos(), e->button(), e->modifiers());
|
mousePressReleased(e->globalPos(), e->button(), e->modifiers());
|
||||||
|
@ -1743,6 +1777,9 @@ void InnerWidget::mousePressReleased(
|
||||||
chooseRow(modifiers, pressedTopicRootId);
|
chooseRow(modifiers, pressedTopicRootId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (auto activated = ClickHandler::unpressed()) {
|
||||||
|
ActivateClickHandler(window(), activated, { button });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::setCollapsedPressed(int pressed) {
|
void InnerWidget::setCollapsedPressed(int pressed) {
|
||||||
|
@ -1825,9 +1862,10 @@ void InnerWidget::moveCancelSearchButtons() {
|
||||||
st::columnMinimalWidthLeft - _narrowWidth);
|
st::columnMinimalWidthLeft - _narrowWidth);
|
||||||
const auto left = widthForCancelButton - st::dialogsSearchInSkip - _cancelSearchInChat->width();
|
const auto left = widthForCancelButton - st::dialogsSearchInSkip - _cancelSearchInChat->width();
|
||||||
const auto top = (st::dialogsSearchInHeight - st::dialogsCancelSearchInPeer.height) / 2;
|
const auto top = (st::dialogsSearchInHeight - st::dialogsCancelSearchInPeer.height) / 2;
|
||||||
_cancelSearchInChat->moveToLeft(left, st::searchedBarHeight + top);
|
const auto skip = st::searchedBarHeight + (_searchTags ? _searchTags->height() : 0);
|
||||||
const auto skip = _searchInChat ? (st::searchedBarHeight + st::dialogsSearchInHeight + st::lineWidth) : 0;
|
_cancelSearchInChat->moveToLeft(left, skip + top);
|
||||||
_cancelSearchFromUser->moveToLeft(left, skip + top);
|
const auto next = _searchInChat ? (skip + st::dialogsSearchInHeight + st::lineWidth) : 0;
|
||||||
|
_cancelSearchFromUser->moveToLeft(left, next + top);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::dialogRowReplaced(
|
void InnerWidget::dialogRowReplaced(
|
||||||
|
@ -2330,7 +2368,9 @@ void InnerWidget::applyFilterUpdate(QString newFilter, bool force) {
|
||||||
newFilter = words.isEmpty() ? QString() : words.join(' ');
|
newFilter = words.isEmpty() ? QString() : words.join(' ');
|
||||||
if (newFilter != _filter || force) {
|
if (newFilter != _filter || force) {
|
||||||
_filter = newFilter;
|
_filter = newFilter;
|
||||||
if (_filter.isEmpty() && !_searchFromPeer) {
|
if (_filter.isEmpty()
|
||||||
|
&& !_searchFromPeer
|
||||||
|
&& _searchTagsSelected.empty()) {
|
||||||
clearFilter();
|
clearFilter();
|
||||||
} else {
|
} else {
|
||||||
setState(WidgetState::Filtered);
|
setState(WidgetState::Filtered);
|
||||||
|
@ -2350,7 +2390,9 @@ void InnerWidget::applyFilterUpdate(QString newFilter, bool force) {
|
||||||
top += i->row->height();
|
top += i->row->height();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (!_searchInChat && !_searchFromPeer && !words.isEmpty()) {
|
if (!_searchInChat
|
||||||
|
&& !_searchFromPeer
|
||||||
|
&& !words.isEmpty()) {
|
||||||
if (_savedSublists) {
|
if (_savedSublists) {
|
||||||
const auto owner = &session().data();
|
const auto owner = &session().data();
|
||||||
append(owner->savedMessages().chatsList()->indexed());
|
append(owner->savedMessages().chatsList()->indexed());
|
||||||
|
@ -2791,6 +2833,11 @@ void InnerWidget::refresh(bool toTop) {
|
||||||
return refreshWithCollapsedRows(toTop);
|
return refreshWithCollapsedRows(toTop);
|
||||||
}
|
}
|
||||||
refreshEmptyLabel();
|
refreshEmptyLabel();
|
||||||
|
if (_searchTags) {
|
||||||
|
_searchTagsLeft = st::dialogsFilterSkip
|
||||||
|
+ st::dialogsFilterPadding.x();
|
||||||
|
_searchTags->resizeToWidth(width() - 2 * _searchTagsLeft);
|
||||||
|
}
|
||||||
auto h = 0;
|
auto h = 0;
|
||||||
if (_state == WidgetState::Default) {
|
if (_state == WidgetState::Default) {
|
||||||
if (_shownList->empty()) {
|
if (_shownList->empty()) {
|
||||||
|
@ -2926,6 +2973,43 @@ void InnerWidget::searchInChat(Key key, PeerData *from) {
|
||||||
} else if (const auto migrateFrom = peer->migrateFrom()) {
|
} else if (const auto migrateFrom = peer->migrateFrom()) {
|
||||||
_searchInMigrated = peer->owner().history(migrateFrom);
|
_searchInMigrated = peer->owner().history(migrateFrom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (peer->isSelf()) {
|
||||||
|
const auto reactions = &peer->owner().reactions();
|
||||||
|
const auto list = [=] {
|
||||||
|
return reactions->list(Data::Reactions::Type::MyTags);
|
||||||
|
};
|
||||||
|
_searchTags = std::make_unique<SearchTags>(
|
||||||
|
&peer->owner(),
|
||||||
|
rpl::single(
|
||||||
|
list()
|
||||||
|
) | rpl::then(
|
||||||
|
reactions->myTagsUpdates() | rpl::map(list)
|
||||||
|
));
|
||||||
|
|
||||||
|
_searchTags->selectedValue(
|
||||||
|
) | rpl::start_with_next([=](std::vector<Data::ReactionId> &&list) {
|
||||||
|
_searchTagsSelected = std::move(list);
|
||||||
|
}, _searchTags->lifetime());
|
||||||
|
|
||||||
|
_searchTags->repaintRequests() | rpl::start_with_next([=] {
|
||||||
|
const auto height = _searchTags->height();
|
||||||
|
update(0, searchInChatOffset(), width(), height);
|
||||||
|
}, _searchTags->lifetime());
|
||||||
|
|
||||||
|
_searchTags->heightValue() | rpl::filter(
|
||||||
|
rpl::mappers::_1 > 0
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
refresh();
|
||||||
|
moveCancelSearchButtons();
|
||||||
|
}, _searchTags->lifetime());
|
||||||
|
} else {
|
||||||
|
_searchTags = nullptr;
|
||||||
|
_searchTagsSelected.clear();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_searchTags = nullptr;
|
||||||
|
_searchTagsSelected.clear();
|
||||||
}
|
}
|
||||||
_searchInChat = key;
|
_searchInChat = key;
|
||||||
_searchFromPeer = from;
|
_searchFromPeer = from;
|
||||||
|
@ -2957,6 +3041,13 @@ void InnerWidget::searchInChat(Key key, PeerData *from) {
|
||||||
_searchInChat || !_filter.isEmpty());
|
_searchInChat || !_filter.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto InnerWidget::searchTagsValue() const
|
||||||
|
-> rpl::producer<std::vector<Data::ReactionId>> {
|
||||||
|
return _searchTags
|
||||||
|
? _searchTags->selectedValue()
|
||||||
|
: rpl::single(std::vector<Data::ReactionId>());
|
||||||
|
}
|
||||||
|
|
||||||
void InnerWidget::refreshSearchInChatLabel() {
|
void InnerWidget::refreshSearchInChatLabel() {
|
||||||
const auto dialog = [&] {
|
const auto dialog = [&] {
|
||||||
if (const auto topic = _searchInChat.topic()) {
|
if (const auto topic = _searchInChat.topic()) {
|
||||||
|
|
|
@ -43,6 +43,7 @@ namespace Data {
|
||||||
class Thread;
|
class Thread;
|
||||||
class Folder;
|
class Folder;
|
||||||
class Forum;
|
class Forum;
|
||||||
|
struct ReactionId;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
namespace Dialogs::Ui {
|
namespace Dialogs::Ui {
|
||||||
|
@ -57,6 +58,7 @@ namespace Dialogs {
|
||||||
class Row;
|
class Row;
|
||||||
class FakeRow;
|
class FakeRow;
|
||||||
class IndexedList;
|
class IndexedList;
|
||||||
|
class SearchTags;
|
||||||
|
|
||||||
struct ChosenRow {
|
struct ChosenRow {
|
||||||
Key key;
|
Key key;
|
||||||
|
@ -138,6 +140,8 @@ public:
|
||||||
[[nodiscard]] bool hasFilteredResults() const;
|
[[nodiscard]] bool hasFilteredResults() const;
|
||||||
|
|
||||||
void searchInChat(Key key, PeerData *from);
|
void searchInChat(Key key, PeerData *from);
|
||||||
|
[[nodiscard]] auto searchTagsValue() const
|
||||||
|
-> rpl::producer<std::vector<Data::ReactionId>>;
|
||||||
|
|
||||||
void applyFilterUpdate(QString newFilter, bool force = false);
|
void applyFilterUpdate(QString newFilter, bool force = false);
|
||||||
void onHashtagFilterUpdate(QStringView newFilter);
|
void onHashtagFilterUpdate(QStringView newFilter);
|
||||||
|
@ -325,6 +329,7 @@ private:
|
||||||
[[nodiscard]] int filteredIndex(int y) const;
|
[[nodiscard]] int filteredIndex(int y) const;
|
||||||
[[nodiscard]] int filteredHeight(int till = -1) const;
|
[[nodiscard]] int filteredHeight(int till = -1) const;
|
||||||
[[nodiscard]] int peerSearchOffset() const;
|
[[nodiscard]] int peerSearchOffset() const;
|
||||||
|
[[nodiscard]] int searchInChatOffset() const;
|
||||||
[[nodiscard]] int searchedOffset() const;
|
[[nodiscard]] int searchedOffset() const;
|
||||||
[[nodiscard]] int searchInChatSkip() const;
|
[[nodiscard]] int searchInChatSkip() const;
|
||||||
|
|
||||||
|
@ -482,6 +487,9 @@ private:
|
||||||
mutable Ui::PeerUserpicView _searchFromUserUserpic;
|
mutable Ui::PeerUserpicView _searchFromUserUserpic;
|
||||||
Ui::Text::String _searchInChatText;
|
Ui::Text::String _searchInChatText;
|
||||||
Ui::Text::String _searchFromUserText;
|
Ui::Text::String _searchFromUserText;
|
||||||
|
std::unique_ptr<SearchTags> _searchTags;
|
||||||
|
std::vector<Data::ReactionId> _searchTagsSelected;
|
||||||
|
int _searchTagsLeft = 0;
|
||||||
RowDescriptor _menuRow;
|
RowDescriptor _menuRow;
|
||||||
|
|
||||||
base::flat_map<
|
base::flat_map<
|
||||||
|
|
246
Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp
Normal file
246
Telegram/SourceFiles/dialogs/dialogs_search_tags.cpp
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "dialogs/dialogs_search_tags.h"
|
||||||
|
|
||||||
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
|
#include "data/data_document.h"
|
||||||
|
#include "data/data_message_reactions.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "history/view/reactions/history_view_reactions.h"
|
||||||
|
#include "ui/effects/animation_value.h"
|
||||||
|
#include "ui/power_saving.h"
|
||||||
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_dialogs.h"
|
||||||
|
|
||||||
|
namespace Dialogs {
|
||||||
|
|
||||||
|
struct SearchTags::Tag {
|
||||||
|
Data::ReactionId id;
|
||||||
|
std::unique_ptr<Ui::Text::CustomEmoji> custom;
|
||||||
|
mutable QImage image;
|
||||||
|
QRect geometry;
|
||||||
|
ClickHandlerPtr link;
|
||||||
|
bool selected = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
SearchTags::SearchTags(
|
||||||
|
not_null<Data::Session*> owner,
|
||||||
|
rpl::producer<std::vector<Data::Reaction>> tags)
|
||||||
|
: _owner(owner) {
|
||||||
|
std::move(
|
||||||
|
tags
|
||||||
|
) | rpl::start_with_next([=](const std::vector<Data::Reaction> &list) {
|
||||||
|
fill(list);
|
||||||
|
}, _lifetime);
|
||||||
|
|
||||||
|
style::PaletteChanged(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
_normalBg = _selectedBg = QImage();
|
||||||
|
}, _lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchTags::~SearchTags() = default;
|
||||||
|
|
||||||
|
void SearchTags::fill(const std::vector<Data::Reaction> &list) {
|
||||||
|
const auto selected = collectSelected();
|
||||||
|
_tags.clear();
|
||||||
|
_tags.reserve(list.size());
|
||||||
|
const auto link = [&](Data::ReactionId id) {
|
||||||
|
return std::make_shared<LambdaClickHandler>(crl::guard(this, [=] {
|
||||||
|
const auto i = ranges::find(_tags, id, &Tag::id);
|
||||||
|
if (i != end(_tags)) {
|
||||||
|
i->selected = !i->selected;
|
||||||
|
_selectedChanges.fire({});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
for (const auto &reaction : list) {
|
||||||
|
const auto id = reaction.id;
|
||||||
|
const auto customId = id.custom();
|
||||||
|
_tags.push_back({
|
||||||
|
.id = id,
|
||||||
|
.custom = (customId
|
||||||
|
? _owner->customEmojiManager().create(
|
||||||
|
customId,
|
||||||
|
[=] { _repaintRequests.fire({}); })
|
||||||
|
: nullptr),
|
||||||
|
.link = link(id),
|
||||||
|
.selected = ranges::contains(selected, id),
|
||||||
|
});
|
||||||
|
if (!customId) {
|
||||||
|
_owner->reactions().preloadImageFor(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_width > 0) {
|
||||||
|
layout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SearchTags::layout() {
|
||||||
|
Expects(_width > 0);
|
||||||
|
|
||||||
|
const auto &bg = validateBg(false);
|
||||||
|
const auto skip = st::dialogsSearchTagSkip;
|
||||||
|
const auto size = bg.size() / bg.devicePixelRatio();
|
||||||
|
const auto xsingle = size.width() + skip.x();
|
||||||
|
const auto ysingle = size.height() + skip.y();
|
||||||
|
const auto columns = std::max((_width + skip.x()) / xsingle, 1);
|
||||||
|
const auto rows = (_tags.size() + columns - 1) / columns;
|
||||||
|
for (auto row = 0; row != rows; ++row) {
|
||||||
|
for (auto column = 0; column != columns; ++column) {
|
||||||
|
const auto index = row * columns + column;
|
||||||
|
if (index >= _tags.size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const auto x = column * xsingle;
|
||||||
|
const auto y = row * ysingle;
|
||||||
|
_tags[index].geometry = QRect(QPoint(x, y), size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto bottom = st::dialogsSearchTagBottom;
|
||||||
|
_height = rows ? (rows * ysingle - skip.y() + bottom) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SearchTags::resizeToWidth(int width) {
|
||||||
|
if (_width == width || width <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_width = width;
|
||||||
|
layout();
|
||||||
|
}
|
||||||
|
|
||||||
|
int SearchTags::height() const {
|
||||||
|
return _height.current();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> SearchTags::heightValue() const {
|
||||||
|
return _height.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> SearchTags::repaintRequests() const {
|
||||||
|
return _repaintRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
ClickHandlerPtr SearchTags::lookupHandler(QPoint point) const {
|
||||||
|
for (const auto &tag : _tags) {
|
||||||
|
if (tag.geometry.contains(point.x(), point.y())) {
|
||||||
|
return tag.link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SearchTags::selectedValue() const
|
||||||
|
-> rpl::producer<std::vector<Data::ReactionId>> {
|
||||||
|
return _selectedChanges.events() | rpl::map([=] {
|
||||||
|
return collectSelected();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SearchTags::paintCustomFrame(
|
||||||
|
QPainter &p,
|
||||||
|
not_null<Ui::Text::CustomEmoji*> emoji,
|
||||||
|
QPoint innerTopLeft,
|
||||||
|
crl::time now,
|
||||||
|
bool paused,
|
||||||
|
const QColor &textColor) const {
|
||||||
|
if (_customCache.isNull()) {
|
||||||
|
using namespace Ui::Text;
|
||||||
|
const auto size = st::emojiSize;
|
||||||
|
const auto factor = style::DevicePixelRatio();
|
||||||
|
const auto adjusted = AdjustCustomEmojiSize(size);
|
||||||
|
_customCache = QImage(
|
||||||
|
QSize(adjusted, adjusted) * factor,
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
_customCache.setDevicePixelRatio(factor);
|
||||||
|
_customSkip = (size - adjusted) / 2;
|
||||||
|
}
|
||||||
|
_customCache.fill(Qt::transparent);
|
||||||
|
auto q = QPainter(&_customCache);
|
||||||
|
emoji->paint(q, {
|
||||||
|
.textColor = textColor,
|
||||||
|
.now = now,
|
||||||
|
.paused = paused || On(PowerSaving::kEmojiChat),
|
||||||
|
});
|
||||||
|
q.end();
|
||||||
|
_customCache = Images::Round(
|
||||||
|
std::move(_customCache),
|
||||||
|
(Images::Option::RoundLarge
|
||||||
|
| Images::Option::RoundSkipTopRight
|
||||||
|
| Images::Option::RoundSkipBottomRight));
|
||||||
|
|
||||||
|
p.drawImage(
|
||||||
|
innerTopLeft + QPoint(_customSkip, _customSkip),
|
||||||
|
_customCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SearchTags::paint(
|
||||||
|
QPainter &p,
|
||||||
|
QPoint position,
|
||||||
|
crl::time now,
|
||||||
|
bool paused) const {
|
||||||
|
const auto size = st::reactionInlineSize;
|
||||||
|
const auto skip = (size - st::reactionInlineImage) / 2;
|
||||||
|
const auto padding = st::reactionInlinePadding;
|
||||||
|
for (const auto &tag : _tags) {
|
||||||
|
const auto geometry = tag.geometry.translated(position);
|
||||||
|
p.drawImage(geometry.topLeft(), validateBg(tag.selected));
|
||||||
|
if (!tag.custom && tag.image.isNull()) {
|
||||||
|
tag.image = _owner->reactions().resolveImageFor(
|
||||||
|
tag.id,
|
||||||
|
::Data::Reactions::ImageSize::InlineList);
|
||||||
|
}
|
||||||
|
const auto inner = geometry.marginsRemoved(padding);
|
||||||
|
const auto image = QRect(
|
||||||
|
inner.topLeft() + QPoint(skip, skip),
|
||||||
|
QSize(st::reactionInlineImage, st::reactionInlineImage));
|
||||||
|
if (const auto custom = tag.custom.get()) {
|
||||||
|
const auto textFg = tag.selected
|
||||||
|
? st::dialogsNameFgActive->c
|
||||||
|
: st::dialogsNameFgOver->c;
|
||||||
|
paintCustomFrame(
|
||||||
|
p,
|
||||||
|
custom,
|
||||||
|
inner.topLeft(),
|
||||||
|
now,
|
||||||
|
paused,
|
||||||
|
textFg);
|
||||||
|
} else if (!tag.image.isNull()) {
|
||||||
|
p.drawImage(image.topLeft(), tag.image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const QImage &SearchTags::validateBg(bool selected) const {
|
||||||
|
using namespace HistoryView::Reactions;
|
||||||
|
auto &image = selected ? _selectedBg : _normalBg;
|
||||||
|
if (image.isNull()) {
|
||||||
|
const auto tagBg = selected
|
||||||
|
? st::dialogsBgActive->c
|
||||||
|
: st::dialogsBgOver->c;
|
||||||
|
const auto dotBg = selected
|
||||||
|
? anim::with_alpha(tagBg, InlineList::TagDotAlpha())
|
||||||
|
: st::windowSubTextFg->c;
|
||||||
|
image = InlineList::PrepareTagBg(tagBg, dotBg);
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Data::ReactionId> SearchTags::collectSelected() const {
|
||||||
|
return _tags | ranges::views::filter(
|
||||||
|
&Tag::selected
|
||||||
|
) | ranges::views::transform(
|
||||||
|
&Tag::id
|
||||||
|
) | ranges::to_vector;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::lifetime &SearchTags::lifetime() {
|
||||||
|
return _lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Dialogs
|
78
Telegram/SourceFiles/dialogs/dialogs_search_tags.h
Normal file
78
Telegram/SourceFiles/dialogs/dialogs_search_tags.h
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/weak_ptr.h"
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
class Session;
|
||||||
|
struct Reaction;
|
||||||
|
struct ReactionId;
|
||||||
|
} // namespace Data
|
||||||
|
|
||||||
|
namespace Ui::Text {
|
||||||
|
class CustomEmoji;
|
||||||
|
} // namespace Ui::Text
|
||||||
|
|
||||||
|
namespace Dialogs {
|
||||||
|
|
||||||
|
class SearchTags final : public base::has_weak_ptr {
|
||||||
|
public:
|
||||||
|
SearchTags(
|
||||||
|
not_null<Data::Session*> owner,
|
||||||
|
rpl::producer<std::vector<Data::Reaction>> tags);
|
||||||
|
~SearchTags();
|
||||||
|
|
||||||
|
void resizeToWidth(int width);
|
||||||
|
[[nodiscard]] int height() const;
|
||||||
|
[[nodiscard]] rpl::producer<int> heightValue() const;
|
||||||
|
[[nodiscard]] rpl::producer<> repaintRequests() const;
|
||||||
|
|
||||||
|
[[nodiscard]] ClickHandlerPtr lookupHandler(QPoint point) const;
|
||||||
|
[[nodiscard]] auto selectedValue() const
|
||||||
|
-> rpl::producer<std::vector<Data::ReactionId>>;
|
||||||
|
|
||||||
|
void paint(
|
||||||
|
QPainter &p,
|
||||||
|
QPoint position,
|
||||||
|
crl::time now,
|
||||||
|
bool paused) const;
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::lifetime &lifetime();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Tag;
|
||||||
|
|
||||||
|
void fill(const std::vector<Data::Reaction> &list);
|
||||||
|
void paintCustomFrame(
|
||||||
|
QPainter &p,
|
||||||
|
not_null<Ui::Text::CustomEmoji*> emoji,
|
||||||
|
QPoint innerTopLeft,
|
||||||
|
crl::time now,
|
||||||
|
bool paused,
|
||||||
|
const QColor &textColor) const;
|
||||||
|
void layout();
|
||||||
|
[[nodiscard]] std::vector<Data::ReactionId> collectSelected() const;
|
||||||
|
[[nodiscard]] const QImage &validateBg(bool selected) const;
|
||||||
|
|
||||||
|
const not_null<Data::Session*> _owner;
|
||||||
|
std::vector<Tag> _tags;
|
||||||
|
rpl::event_stream<> _selectedChanges;
|
||||||
|
rpl::event_stream<> _repaintRequests;
|
||||||
|
mutable QImage _normalBg;
|
||||||
|
mutable QImage _selectedBg;
|
||||||
|
mutable QImage _customCache;
|
||||||
|
mutable int _customSkip = 0;
|
||||||
|
rpl::variable<int> _height;
|
||||||
|
int _width = 0;
|
||||||
|
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Dialogs
|
|
@ -1713,7 +1713,7 @@ void Widget::loadMoreBlockedByDate() {
|
||||||
bool Widget::searchMessages(bool searchCache) {
|
bool Widget::searchMessages(bool searchCache) {
|
||||||
auto result = false;
|
auto result = false;
|
||||||
auto q = currentSearchQuery().trimmed();
|
auto q = currentSearchQuery().trimmed();
|
||||||
if (q.isEmpty() && !_searchFromAuthor) {
|
if (q.isEmpty() && !_searchFromAuthor && _searchTags.empty()) {
|
||||||
cancelSearchRequest();
|
cancelSearchRequest();
|
||||||
_api.request(base::take(_peerSearchRequest)).cancel();
|
_api.request(base::take(_peerSearchRequest)).cancel();
|
||||||
_api.request(base::take(_topicSearchRequest)).cancel();
|
_api.request(base::take(_topicSearchRequest)).cancel();
|
||||||
|
@ -1730,6 +1730,7 @@ bool Widget::searchMessages(bool searchCache) {
|
||||||
if (i != _searchCache.end()) {
|
if (i != _searchCache.end()) {
|
||||||
_searchQuery = q;
|
_searchQuery = q;
|
||||||
_searchQueryFrom = _searchFromAuthor;
|
_searchQueryFrom = _searchFromAuthor;
|
||||||
|
_searchQueryTags = _searchTags;
|
||||||
_searchNextRate = 0;
|
_searchNextRate = 0;
|
||||||
_searchFull = _searchFullMigrated = false;
|
_searchFull = _searchFullMigrated = false;
|
||||||
cancelSearchRequest();
|
cancelSearchRequest();
|
||||||
|
@ -1741,9 +1742,12 @@ bool Widget::searchMessages(bool searchCache) {
|
||||||
0);
|
0);
|
||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
} else if (_searchQuery != q || _searchQueryFrom != _searchFromAuthor) {
|
} else if (_searchQuery != q
|
||||||
|
|| _searchQueryFrom != _searchFromAuthor
|
||||||
|
|| _searchQueryTags != _searchTags) {
|
||||||
_searchQuery = q;
|
_searchQuery = q;
|
||||||
_searchQueryFrom = _searchFromAuthor;
|
_searchQueryFrom = _searchFromAuthor;
|
||||||
|
_searchQueryTags = _searchTags;
|
||||||
_searchNextRate = 0;
|
_searchNextRate = 0;
|
||||||
_searchFull = _searchFullMigrated = false;
|
_searchFull = _searchFullMigrated = false;
|
||||||
cancelSearchRequest();
|
cancelSearchRequest();
|
||||||
|
@ -1757,14 +1761,20 @@ bool Widget::searchMessages(bool searchCache) {
|
||||||
using Flag = MTPmessages_Search::Flag;
|
using Flag = MTPmessages_Search::Flag;
|
||||||
_searchRequest = session().api().request(MTPmessages_Search(
|
_searchRequest = session().api().request(MTPmessages_Search(
|
||||||
MTP_flags((topic ? Flag::f_top_msg_id : Flag())
|
MTP_flags((topic ? Flag::f_top_msg_id : Flag())
|
||||||
| (_searchQueryFrom ? Flag::f_from_id : Flag())),
|
| (_searchQueryFrom ? Flag::f_from_id : Flag())
|
||||||
|
| (_searchQueryTags.empty()
|
||||||
|
? Flag()
|
||||||
|
: Flag::f_saved_reaction)),
|
||||||
peer->input,
|
peer->input,
|
||||||
MTP_string(_searchQuery),
|
MTP_string(_searchQuery),
|
||||||
(_searchQueryFrom
|
(_searchQueryFrom
|
||||||
? _searchQueryFrom->input
|
? _searchQueryFrom->input
|
||||||
: MTP_inputPeerEmpty()),
|
: MTP_inputPeerEmpty()),
|
||||||
MTPInputPeer(), // saved_peer_id
|
MTPInputPeer(), // saved_peer_id
|
||||||
MTPVector<MTPReaction>(), // saved_reaction
|
MTP_vector_from_range(
|
||||||
|
_searchQueryTags | ranges::views::transform(
|
||||||
|
Data::ReactionToMTP
|
||||||
|
)),
|
||||||
MTP_int(topic ? topic->rootId() : 0),
|
MTP_int(topic ? topic->rootId() : 0),
|
||||||
MTP_inputMessagesFilterEmpty(),
|
MTP_inputMessagesFilterEmpty(),
|
||||||
MTP_int(0), // min_date
|
MTP_int(0), // min_date
|
||||||
|
@ -1868,6 +1878,7 @@ bool Widget::searchMessages(bool searchCache) {
|
||||||
bool Widget::searchForPeersRequired(const QString &query) const {
|
bool Widget::searchForPeersRequired(const QString &query) const {
|
||||||
return !_searchInChat
|
return !_searchInChat
|
||||||
&& !_searchFromAuthor
|
&& !_searchFromAuthor
|
||||||
|
&& _searchTags.empty()
|
||||||
&& !_openedForum
|
&& !_openedForum
|
||||||
&& !query.isEmpty()
|
&& !query.isEmpty()
|
||||||
&& (query[0] != '#');
|
&& (query[0] != '#');
|
||||||
|
@ -1876,6 +1887,7 @@ bool Widget::searchForPeersRequired(const QString &query) const {
|
||||||
bool Widget::searchForTopicsRequired(const QString &query) const {
|
bool Widget::searchForTopicsRequired(const QString &query) const {
|
||||||
return !_searchInChat
|
return !_searchInChat
|
||||||
&& !_searchFromAuthor
|
&& !_searchFromAuthor
|
||||||
|
&& _searchTags.empty()
|
||||||
&& _openedForum
|
&& _openedForum
|
||||||
&& !query.isEmpty()
|
&& !query.isEmpty()
|
||||||
&& (query[0] != '#')
|
&& (query[0] != '#')
|
||||||
|
@ -2000,14 +2012,20 @@ void Widget::searchMore() {
|
||||||
using Flag = MTPmessages_Search::Flag;
|
using Flag = MTPmessages_Search::Flag;
|
||||||
_searchRequest = session().api().request(MTPmessages_Search(
|
_searchRequest = session().api().request(MTPmessages_Search(
|
||||||
MTP_flags((topic ? Flag::f_top_msg_id : Flag())
|
MTP_flags((topic ? Flag::f_top_msg_id : Flag())
|
||||||
| (_searchQueryFrom ? Flag::f_from_id : Flag())),
|
| (_searchQueryFrom ? Flag::f_from_id : Flag())
|
||||||
|
| (_searchQueryTags.empty()
|
||||||
|
? Flag()
|
||||||
|
: Flag::f_saved_reaction)),
|
||||||
peer->input,
|
peer->input,
|
||||||
MTP_string(_searchQuery),
|
MTP_string(_searchQuery),
|
||||||
(_searchQueryFrom
|
(_searchQueryFrom
|
||||||
? _searchQueryFrom->input
|
? _searchQueryFrom->input
|
||||||
: MTP_inputPeerEmpty()),
|
: MTP_inputPeerEmpty()),
|
||||||
MTPInputPeer(), // saved_peer_id
|
MTPInputPeer(), // saved_peer_id
|
||||||
MTPVector<MTPReaction>(), // saved_reaction
|
MTP_vector_from_range(
|
||||||
|
_searchQueryTags | ranges::views::transform(
|
||||||
|
Data::ReactionToMTP
|
||||||
|
)),
|
||||||
MTP_int(topic ? topic->rootId() : 0),
|
MTP_int(topic ? topic->rootId() : 0),
|
||||||
MTP_inputMessagesFilterEmpty(),
|
MTP_inputMessagesFilterEmpty(),
|
||||||
MTP_int(0), // min_date
|
MTP_int(0), // min_date
|
||||||
|
@ -2401,7 +2419,7 @@ void Widget::applyFilterUpdate(bool force) {
|
||||||
updateStoriesVisibility();
|
updateStoriesVisibility();
|
||||||
const auto filterText = currentSearchQuery();
|
const auto filterText = currentSearchQuery();
|
||||||
_inner->applyFilterUpdate(filterText, force);
|
_inner->applyFilterUpdate(filterText, force);
|
||||||
if (filterText.isEmpty() && !_searchFromAuthor) {
|
if (filterText.isEmpty() && !_searchFromAuthor && _searchTags.empty()) {
|
||||||
clearSearchCache();
|
clearSearchCache();
|
||||||
}
|
}
|
||||||
_cancelSearch->toggle(!filterText.isEmpty(), anim::type::normal);
|
_cancelSearch->toggle(!filterText.isEmpty(), anim::type::normal);
|
||||||
|
@ -2417,7 +2435,9 @@ void Widget::applyFilterUpdate(bool force) {
|
||||||
_peerSearchQuery = QString();
|
_peerSearchQuery = QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_chooseFromUser->toggled() || _searchFromAuthor) {
|
if (_chooseFromUser->toggled()
|
||||||
|
|| _searchFromAuthor
|
||||||
|
|| !_searchTags.empty()) {
|
||||||
auto switchToChooseFrom = HistoryView::SwitchToChooseFromQuery();
|
auto switchToChooseFrom = HistoryView::SwitchToChooseFromQuery();
|
||||||
if (_lastFilterText != switchToChooseFrom
|
if (_lastFilterText != switchToChooseFrom
|
||||||
&& switchToChooseFrom.startsWith(_lastFilterText)
|
&& switchToChooseFrom.startsWith(_lastFilterText)
|
||||||
|
@ -2619,6 +2639,18 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) {
|
||||||
controller()->closeFolder();
|
controller()->closeFolder();
|
||||||
}
|
}
|
||||||
_inner->searchInChat(_searchInChat, _searchFromAuthor);
|
_inner->searchInChat(_searchInChat, _searchFromAuthor);
|
||||||
|
_searchTagsLifetime = _inner->searchTagsValue(
|
||||||
|
) | rpl::start_with_next([=](std::vector<Data::ReactionId> &&list) {
|
||||||
|
if (_searchTags != list) {
|
||||||
|
clearSearchCache();
|
||||||
|
_searchTags = std::move(list);
|
||||||
|
if (_searchTags.empty()) {
|
||||||
|
applyFilterUpdate(true);
|
||||||
|
} else {
|
||||||
|
searchMessages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
if (_subsectionTopBar) {
|
if (_subsectionTopBar) {
|
||||||
_subsectionTopBar->searchEnableJumpToDate(
|
_subsectionTopBar->searchEnableJumpToDate(
|
||||||
_openedForum && _searchInChat);
|
_openedForum && _searchInChat);
|
||||||
|
@ -2639,6 +2671,7 @@ void Widget::clearSearchCache() {
|
||||||
}
|
}
|
||||||
_searchQuery = QString();
|
_searchQuery = QString();
|
||||||
_searchQueryFrom = nullptr;
|
_searchQueryFrom = nullptr;
|
||||||
|
_searchQueryTags.clear();
|
||||||
_topicSearchQuery = QString();
|
_topicSearchQuery = QString();
|
||||||
_topicSearchOffsetDate = 0;
|
_topicSearchOffsetDate = 0;
|
||||||
_topicSearchOffsetId = _topicSearchOffsetTopicId = 0;
|
_topicSearchOffsetId = _topicSearchOffsetTopicId = 0;
|
||||||
|
|
|
@ -22,6 +22,7 @@ class Error;
|
||||||
namespace Data {
|
namespace Data {
|
||||||
class Forum;
|
class Forum;
|
||||||
enum class StorySourcesList : uchar;
|
enum class StorySourcesList : uchar;
|
||||||
|
struct ReactionId;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
|
@ -285,6 +286,8 @@ private:
|
||||||
Dialogs::Key _searchInChat;
|
Dialogs::Key _searchInChat;
|
||||||
History *_searchInMigrated = nullptr;
|
History *_searchInMigrated = nullptr;
|
||||||
PeerData *_searchFromAuthor = nullptr;
|
PeerData *_searchFromAuthor = nullptr;
|
||||||
|
std::vector<Data::ReactionId> _searchTags;
|
||||||
|
rpl::lifetime _searchTagsLifetime;
|
||||||
QString _lastFilterText;
|
QString _lastFilterText;
|
||||||
|
|
||||||
rpl::event_stream<rpl::producer<Stories::Content>> _storiesContents;
|
rpl::event_stream<rpl::producer<Stories::Content>> _storiesContents;
|
||||||
|
@ -313,6 +316,7 @@ private:
|
||||||
|
|
||||||
QString _searchQuery;
|
QString _searchQuery;
|
||||||
PeerData *_searchQueryFrom = nullptr;
|
PeerData *_searchQueryFrom = nullptr;
|
||||||
|
std::vector<Data::ReactionId> _searchQueryTags;
|
||||||
int32 _searchNextRate = 0;
|
int32 _searchNextRate = 0;
|
||||||
bool _searchFull = false;
|
bool _searchFull = false;
|
||||||
bool _searchFullMigrated = false;
|
bool _searchFullMigrated = false;
|
||||||
|
|
|
@ -507,12 +507,11 @@ void InlineList::paint(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InlineList::validateTagBg(const QColor &color) const {
|
float64 InlineList::TagDotAlpha() {
|
||||||
if (!_tagBg.isNull() && _tagBgColor == color) {
|
return 0.6;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
_tagBgColor = color;
|
|
||||||
|
|
||||||
|
QImage InlineList::PrepareTagBg(QColor tagBg, QColor dotBg) {
|
||||||
const auto padding = st::reactionInlinePadding;
|
const auto padding = st::reactionInlinePadding;
|
||||||
const auto size = st::reactionInlineSize;
|
const auto size = st::reactionInlineSize;
|
||||||
const auto width = padding.left()
|
const auto width = padding.left()
|
||||||
|
@ -522,20 +521,19 @@ void InlineList::validateTagBg(const QColor &color) const {
|
||||||
const auto height = padding.top() + size + padding.bottom();
|
const auto height = padding.top() + size + padding.bottom();
|
||||||
const auto ratio = style::DevicePixelRatio();
|
const auto ratio = style::DevicePixelRatio();
|
||||||
|
|
||||||
auto mask = QImage(
|
auto result = QImage(
|
||||||
QSize(width, height) * ratio,
|
QSize(width, height) * ratio,
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
mask.setDevicePixelRatio(ratio);
|
result.setDevicePixelRatio(ratio);
|
||||||
|
|
||||||
mask.fill(Qt::transparent);
|
result.fill(Qt::transparent);
|
||||||
auto p = QPainter(&mask);
|
auto p = QPainter(&result);
|
||||||
|
|
||||||
auto path = QPainterPath();
|
auto path = QPainterPath();
|
||||||
const auto arrow = st::reactionInlineTagArrow;
|
const auto arrow = st::reactionInlineTagArrow;
|
||||||
const auto rradius = st::reactionInlineTagRightRadius * 1.;
|
const auto rradius = st::reactionInlineTagRightRadius * 1.;
|
||||||
const auto radius = st::reactionInlineTagLeftRadius - rradius;
|
const auto radius = st::reactionInlineTagLeftRadius - rradius;
|
||||||
const auto fg = QColor(255, 255, 255);
|
auto pen = QPen(tagBg);
|
||||||
auto pen = QPen(fg);
|
|
||||||
pen.setWidthF(rradius * 2.);
|
pen.setWidthF(rradius * 2.);
|
||||||
pen.setJoinStyle(Qt::RoundJoin);
|
pen.setJoinStyle(Qt::RoundJoin);
|
||||||
const auto rect = QRectF(0, 0, width, height).marginsRemoved(
|
const auto rect = QRectF(0, 0, width, height).marginsRemoved(
|
||||||
|
@ -548,9 +546,15 @@ void InlineList::validateTagBg(const QColor &color) const {
|
||||||
path.lineTo(right, rect.y() + rect.height() / 2);
|
path.lineTo(right, rect.y() + rect.height() / 2);
|
||||||
path.lineTo(right - arrow, bottom);
|
path.lineTo(right - arrow, bottom);
|
||||||
path.lineTo(rect.x() + radius, bottom);
|
path.lineTo(rect.x() + radius, bottom);
|
||||||
path.arcTo(QRectF(rect.x(), bottom - radius * 2, radius * 2, radius * 2), 270, -90);
|
path.arcTo(
|
||||||
|
QRectF(rect.x(), bottom - radius * 2, radius * 2, radius * 2),
|
||||||
|
270,
|
||||||
|
-90);
|
||||||
path.lineTo(rect.x(), rect.y() + radius);
|
path.lineTo(rect.x(), rect.y() + radius);
|
||||||
path.arcTo(QRectF(rect.x(), rect.y(), radius * 2, radius * 2), 180, -90);
|
path.arcTo(
|
||||||
|
QRectF(rect.x(), rect.y(), radius * 2, radius * 2),
|
||||||
|
180,
|
||||||
|
-90);
|
||||||
path.closeSubpath();
|
path.closeSubpath();
|
||||||
|
|
||||||
const auto dsize = st::reactionInlineTagDot;
|
const auto dsize = st::reactionInlineTagDot;
|
||||||
|
@ -563,16 +567,26 @@ void InlineList::validateTagBg(const QColor &color) const {
|
||||||
auto hq = PainterHighQualityEnabler(p);
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||||
p.setPen(pen);
|
p.setPen(pen);
|
||||||
p.setBrush(fg);
|
p.setBrush(tagBg);
|
||||||
p.drawPath(path);
|
p.drawPath(path);
|
||||||
|
|
||||||
p.setPen(Qt::NoPen);
|
p.setPen(Qt::NoPen);
|
||||||
p.setBrush(QColor(255, 255, 255, 255 * 0.6));
|
p.setBrush(dotBg);
|
||||||
p.drawEllipse(dot);
|
p.drawEllipse(dot);
|
||||||
|
|
||||||
p.end();
|
p.end();
|
||||||
|
|
||||||
_tagBg = style::colorizeImage(mask, color);
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InlineList::validateTagBg(const QColor &color) const {
|
||||||
|
if (!_tagBg.isNull() && _tagBgColor == color) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_tagBgColor = color;
|
||||||
|
auto dot = color;
|
||||||
|
dot.setAlphaF(dot.alphaF() * TagDotAlpha());
|
||||||
|
_tagBg = PrepareTagBg(color, anim::with_alpha(color, TagDotAlpha()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void InlineList::paintSingleBg(
|
void InlineList::paintSingleBg(
|
||||||
|
|
|
@ -93,6 +93,9 @@ public:
|
||||||
ReactionId,
|
ReactionId,
|
||||||
std::unique_ptr<Ui::ReactionFlyAnimation>> animations);
|
std::unique_ptr<Ui::ReactionFlyAnimation>> animations);
|
||||||
|
|
||||||
|
[[nodiscard]] static float64 TagDotAlpha();
|
||||||
|
[[nodiscard]] static QImage PrepareTagBg(QColor tagBg, QColor dotBg);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Userpics {
|
struct Userpics {
|
||||||
QImage image;
|
QImage image;
|
||||||
|
|
Loading…
Add table
Reference in a new issue