Support dynamic search tabs with emoji.

This commit is contained in:
John Preston 2024-05-21 09:36:56 +04:00
parent 7b7438cd7b
commit 279db771cf
9 changed files with 158 additions and 60 deletions

View file

@ -48,7 +48,8 @@ class DefaultIconEmoji final : public Ui::Text::CustomEmoji {
public: public:
DefaultIconEmoji( DefaultIconEmoji(
rpl::producer<DefaultIcon> value, rpl::producer<DefaultIcon> value,
Fn<void()> repaint); Fn<void()> repaint,
Data::CustomEmojiSizeTag tag);
int width() override; int width() override;
QString entityData() override; QString entityData() override;
@ -61,14 +62,17 @@ public:
private: private:
DefaultIcon _icon = {}; DefaultIcon _icon = {};
QImage _image; QImage _image;
Data::CustomEmojiSizeTag _tag = {};
rpl::lifetime _lifetime; rpl::lifetime _lifetime;
}; };
DefaultIconEmoji::DefaultIconEmoji( DefaultIconEmoji::DefaultIconEmoji(
rpl::producer<DefaultIcon> value, rpl::producer<DefaultIcon> value,
Fn<void()> repaint) { Fn<void()> repaint,
Data::CustomEmojiSizeTag tag)
: _tag(tag) {
std::move(value) | rpl::start_with_next([=](DefaultIcon value) { std::move(value) | rpl::start_with_next([=](DefaultIcon value) {
_icon = value; _icon = value;
_image = QImage(); _image = QImage();
@ -85,19 +89,22 @@ QString DefaultIconEmoji::entityData() {
} }
void DefaultIconEmoji::paint(QPainter &p, const Context &context) { void DefaultIconEmoji::paint(QPainter &p, const Context &context) {
const auto &st = (_tag == Data::CustomEmojiSizeTag::Normal)
? st::normalForumTopicIcon
: st::defaultForumTopicIcon;
if (_image.isNull()) { if (_image.isNull()) {
_image = Data::IsForumGeneralIconTitle(_icon.title) _image = Data::IsForumGeneralIconTitle(_icon.title)
? Data::ForumTopicGeneralIconFrame( ? Data::ForumTopicGeneralIconFrame(
st::defaultForumTopicIcon.size, st.size,
Data::ParseForumGeneralIconColor(_icon.colorId)) Data::ParseForumGeneralIconColor(_icon.colorId))
: Data::ForumTopicIconFrame( : Data::ForumTopicIconFrame(_icon.colorId, _icon.title, st);
_icon.colorId,
_icon.title,
st::defaultForumTopicIcon);
} }
const auto esize = Ui::Emoji::GetSizeLarge() / style::DevicePixelRatio(); const auto full = (_tag == Data::CustomEmojiSizeTag::Normal)
? Ui::Emoji::GetSizeNormal()
: Ui::Emoji::GetSizeLarge();
const auto esize = full / style::DevicePixelRatio();
const auto customSize = Ui::Text::AdjustCustomEmojiSize(esize); const auto customSize = Ui::Text::AdjustCustomEmojiSize(esize);
const auto skip = (customSize - st::defaultForumTopicIcon.size) / 2; const auto skip = (customSize - st.size) / 2;
p.drawImage(context.position + QPoint(skip, skip), _image); p.drawImage(context.position + QPoint(skip, skip), _image);
} }
@ -262,7 +269,8 @@ struct IconSelector {
if (id == kDefaultIconId) { if (id == kDefaultIconId) {
return std::make_unique<DefaultIconEmoji>( return std::make_unique<DefaultIconEmoji>(
rpl::duplicate(defaultIcon), rpl::duplicate(defaultIcon),
std::move(repaint)); std::move(repaint),
tag);
} }
return manager->create(id, std::move(repaint), tag); return manager->create(id, std::move(repaint), tag);
}; };
@ -576,8 +584,10 @@ void EditForumTopicBox(
std::unique_ptr<Ui::Text::CustomEmoji> MakeTopicIconEmoji( std::unique_ptr<Ui::Text::CustomEmoji> MakeTopicIconEmoji(
Data::TopicIconDescriptor descriptor, Data::TopicIconDescriptor descriptor,
Fn<void()> repaint) { Fn<void()> repaint,
Data::CustomEmojiSizeTag tag) {
return std::make_unique<DefaultIconEmoji>( return std::make_unique<DefaultIconEmoji>(
rpl::single(descriptor), rpl::single(descriptor),
std::move(repaint)); std::move(repaint),
tag);
} }

View file

@ -13,6 +13,7 @@ class History;
namespace Data { namespace Data {
struct TopicIconDescriptor; struct TopicIconDescriptor;
enum class CustomEmojiSizeTag : uchar;
} // namespace Data } // namespace Data
namespace Window { namespace Window {
@ -32,4 +33,5 @@ void EditForumTopicBox(
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> MakeTopicIconEmoji( [[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> MakeTopicIconEmoji(
Data::TopicIconDescriptor descriptor, Data::TopicIconDescriptor descriptor,
Fn<void()> repaint); Fn<void()> repaint,
Data::CustomEmojiSizeTag tag);

View file

@ -542,7 +542,7 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
const auto size = EmojiSizeFromTag(tag) / ratio; const auto size = EmojiSizeFromTag(tag) / ratio;
return userpic(data, std::move(update), size); return userpic(data, std::move(update), size);
} else if (const auto parsed = Data::ParseTopicIconEmojiEntity(data)) { } else if (const auto parsed = Data::ParseTopicIconEmojiEntity(data)) {
return MakeTopicIconEmoji(parsed, std::move(update)); return MakeTopicIconEmoji(parsed, std::move(update), tag);
} }
const auto parsed = ParseCustomEmojiData(data); const auto parsed = ParseCustomEmojiData(data);
return parsed return parsed

View file

@ -46,6 +46,11 @@ defaultForumTopicIcon: ForumTopicIcon {
font: font(bold 11px); font: font(bold 11px);
textTop: 2px; textTop: 2px;
} }
normalForumTopicIcon: ForumTopicIcon {
size: 19px;
font: font(bold 10px);
textTop: 2px;
}
largeForumTopicIcon: ForumTopicIcon { largeForumTopicIcon: ForumTopicIcon {
size: 26px; size: 26px;
font: font(bold 13px); font: font(bold 13px);

View file

@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "base/event_filter.h" #include "base/event_filter.h"
#include "core/application.h" #include "core/application.h"
#include "core/ui_integration.h"
#include "core/update_checker.h" #include "core/update_checker.h"
#include "core/shortcuts.h" #include "core/shortcuts.h"
#include "boxes/peer_list_box.h" #include "boxes/peer_list_box.h"
@ -1230,9 +1231,17 @@ void Widget::updateSearchTabs() {
} }
return; return;
} else if (!_searchTabs) { } else if (!_searchTabs) {
const auto savedSession = &session();
const auto markedTextContext = [=](Fn<void()> repaint) {
return Core::MarkedTextContext{
.session = savedSession,
.customEmojiRepaint = std::move(repaint),
};
};
_searchTabs = std::make_unique<ChatSearchTabs>( _searchTabs = std::make_unique<ChatSearchTabs>(
this, this,
_searchState.tab); _searchState.tab,
std::move(markedTextContext));
_searchTabs->setVisible(!_showAnimation); _searchTabs->setVisible(!_showAnimation);
_searchTabs->tabChanges( _searchTabs->tabChanges(
) | rpl::filter([=](ChatSearchTab tab) { ) | rpl::filter([=](ChatSearchTab tab) {
@ -1250,16 +1259,19 @@ void Widget::updateSearchTabs() {
: _openedForum : _openedForum
? _openedForum->channel().get() ? _openedForum->channel().get()
: nullptr; : nullptr;
const auto topicShortLabel = topic const auto topicShortLabel = !topic
? Ui::Text::SingleCustomEmoji(Data::TopicIconEmojiEntity({ ? TextWithEntities()
: topic->iconId()
? Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(topic->iconId()))
: Ui::Text::SingleCustomEmoji(Data::TopicIconEmojiEntity({
.title = (topic->isGeneral() .title = (topic->isGeneral()
? Data::ForumGeneralIconTitle() ? Data::ForumGeneralIconTitle()
: topic->title()), : topic->title()),
.colorId = (topic->isGeneral() .colorId = (topic->isGeneral()
? Data::ForumGeneralIconColor(st::windowSubTextFg->c) ? Data::ForumGeneralIconColor(st::windowSubTextFg->c)
: topic->colorId()), : topic->colorId()),
})) }));
: TextWithEntities();
const auto peerShortLabel = peer const auto peerShortLabel = peer
? Ui::Text::SingleCustomEmoji( ? Ui::Text::SingleCustomEmoji(
session().data().customEmojiManager().peerUserpicEmojiData( session().data().customEmojiManager().peerUserpicEmojiData(

View file

@ -97,31 +97,19 @@ bool IsHashtagSearchQuery(const QString &query) {
return true; return true;
} }
ChatSearchTabs::ChatSearchTabs(QWidget *parent, ChatSearchTab active) ChatSearchTabs::ChatSearchTabs(
QWidget *parent,
ChatSearchTab active,
Fn<std::any(Fn<void()>)> markedTextContext)
: RpWidget(parent) : RpWidget(parent)
, _tabs(std::make_unique<Ui::SettingsSlider>(this, st::dialogsSearchTabs)) , _tabs(std::make_unique<Ui::SettingsSlider>(this, st::dialogsSearchTabs))
, _shadow(std::make_unique<Ui::PlainShadow>(this)) , _shadow(std::make_unique<Ui::PlainShadow>(this))
, _markedTextContext(std::move(markedTextContext))
, _active(active) { , _active(active) {
for (const auto tab : {
ChatSearchTab::ThisTopic,
ChatSearchTab::ThisPeer,
ChatSearchTab::MyMessages,
ChatSearchTab::PublicPosts,
}) {
_list.push_back({ tab, TabLabel(tab) });
}
_tabs->move(st::dialogsSearchTabsPadding, 0); _tabs->move(st::dialogsSearchTabsPadding, 0);
_tabs->sectionActivated( _tabs->sectionActivated(
) | rpl::start_with_next([=](int index) { ) | rpl::start_with_next([=](int index) {
for (const auto &tab : _list) { _active = _list[index].value;
if (tab.shortLabel.empty()) {
continue;
} else if (!index) {
_active = tab.value;
return;
}
--index;
}
}, lifetime()); }, lifetime());
} }
@ -131,40 +119,73 @@ void ChatSearchTabs::setTabShortLabels(
std::vector<ShortLabel> labels, std::vector<ShortLabel> labels,
ChatSearchTab active, ChatSearchTab active,
ChatSearchPeerTabType peerTabType) { ChatSearchPeerTabType peerTabType) {
for (const auto &label : labels) { const auto &st = st::dialogsSearchTabs;
const auto i = ranges::find(_list, label.tab, &Tab::value); const auto &font = st.labelStyle.font;
Assert(i != end(_list)); _list.clear();
i->shortLabel = std::move(label.label); _list.reserve(labels.size());
if (i->value == ChatSearchTab::ThisPeer) {
i->label = TabLabel(label.tab, peerTabType); auto widthTotal = 0;
for (const auto tab : {
ChatSearchTab::ThisTopic,
ChatSearchTab::ThisPeer,
ChatSearchTab::MyMessages,
ChatSearchTab::PublicPosts,
}) {
const auto i = ranges::find(labels, tab, &ShortLabel::tab);
if (i != end(labels) && !i->label.empty()) {
const auto label = TabLabel(tab, peerTabType);
const auto widthFull = font->width(label) + st.strictSkip;
_list.push_back({
.value = tab,
.label = label,
.shortLabel = i->label,
.widthFull = widthFull,
});
widthTotal += widthFull;
} }
} }
refreshTabs(active); const auto widthSingleEmoji = st::emojiSize + st.strictSkip;
for (const auto tab : {
ChatSearchTab::PublicPosts,
ChatSearchTab::ThisTopic,
ChatSearchTab::ThisPeer,
ChatSearchTab::MyMessages,
}) {
const auto i = ranges::find(_list, tab, &Tab::value);
if (i != end(_list)) {
i->widthThresholdForShort = widthTotal;
widthTotal -= i->widthFull;
widthTotal += widthSingleEmoji;
}
}
refillTabs(active, width());
} }
rpl::producer<ChatSearchTab> ChatSearchTabs::tabChanges() const { rpl::producer<ChatSearchTab> ChatSearchTabs::tabChanges() const {
return _active.changes(); return _active.changes();
} }
void ChatSearchTabs::refreshTabs(ChatSearchTab active) { void ChatSearchTabs::refillTabs(
auto index = 0; ChatSearchTab active,
auto labels = std::vector<QString>(); int newWidth) {
auto labels = std::vector<TextWithEntities>();
const auto available = newWidth - 2 * st::dialogsSearchTabsPadding;
for (const auto &tab : _list) { for (const auto &tab : _list) {
if (tab.value == active) { auto label = (available < tab.widthThresholdForShort)
index = int(labels.size()); ? tab.shortLabel
Assert(!tab.shortLabel.empty()); : TextWithEntities{ tab.label };
labels.push_back(tab.label); labels.push_back(std::move(label));
} else if (!tab.shortLabel.empty()) {
labels.push_back(tab.label);
}
} }
_tabs->setSections(labels); _tabs->setSections(labels, _markedTextContext([=] { update(); }));
_tabs->setActiveSectionFast(index);
resizeToWidth(width()); const auto i = ranges::find(_list, active, &Tab::value);
Assert(i != end(_list));
_tabs->setActiveSectionFast(i - begin(_list));
_tabs->resizeToWidth(newWidth);
} }
int ChatSearchTabs::resizeGetHeight(int newWidth) { int ChatSearchTabs::resizeGetHeight(int newWidth) {
_tabs->resizeToWidth(newWidth); refillTabs(_active.current(), newWidth);
_shadow->setGeometry( _shadow->setGeometry(
0, 0,
_tabs->y() + _tabs->height() - st::lineWidth, _tabs->y() + _tabs->height() - st::lineWidth,

View file

@ -34,7 +34,10 @@ enum class ChatSearchPeerTabType : uchar {
class ChatSearchTabs final : public Ui::RpWidget { class ChatSearchTabs final : public Ui::RpWidget {
public: public:
ChatSearchTabs(QWidget *parent, ChatSearchTab active); ChatSearchTabs(
QWidget *parent,
ChatSearchTab active,
Fn<std::any(Fn<void()>)> markedTextContext);
~ChatSearchTabs(); ~ChatSearchTabs();
// A [custom] emoji to use when there is not enough space for text. // A [custom] emoji to use when there is not enough space for text.
@ -55,14 +58,18 @@ private:
ChatSearchTab value = {}; ChatSearchTab value = {};
QString label; QString label;
TextWithEntities shortLabel; TextWithEntities shortLabel;
int widthFull = 0;
int widthThresholdForShort = 0;
}; };
void refreshTabs(ChatSearchTab active); void refreshTabs(ChatSearchTab active);
void refillTabs(ChatSearchTab active, int newWidth);
int resizeGetHeight(int newWidth) override; int resizeGetHeight(int newWidth) override;
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
const std::unique_ptr<Ui::SettingsSlider> _tabs; const std::unique_ptr<Ui::SettingsSlider> _tabs;
const std::unique_ptr<Ui::PlainShadow> _shadow; const std::unique_ptr<Ui::PlainShadow> _shadow;
const Fn<std::any(Fn<void()>)> _markedTextContext;
std::vector<Tab> _list; std::vector<Tab> _list;
rpl::variable<ChatSearchTab> _active; rpl::variable<ChatSearchTab> _active;

View file

@ -66,6 +66,13 @@ void DiscreteSlider::addSection(const QString &label) {
resizeToWidth(width()); resizeToWidth(width());
} }
void DiscreteSlider::addSection(
const TextWithEntities &label,
const std::any &context) {
_sections.push_back(Section(label, getLabelStyle(), context));
resizeToWidth(width());
}
void DiscreteSlider::setSections(const std::vector<QString> &labels) { void DiscreteSlider::setSections(const std::vector<QString> &labels) {
Assert(!labels.empty()); Assert(!labels.empty());
@ -73,6 +80,22 @@ void DiscreteSlider::setSections(const std::vector<QString> &labels) {
for (const auto &label : labels) { for (const auto &label : labels) {
_sections.push_back(Section(label, getLabelStyle())); _sections.push_back(Section(label, getLabelStyle()));
} }
refresh();
}
void DiscreteSlider::setSections(
const std::vector<TextWithEntities> &labels,
const std::any &context) {
Assert(!labels.empty());
_sections.clear();
for (const auto &label : labels) {
_sections.push_back(Section(label, getLabelStyle(), context));
}
refresh();
}
void DiscreteSlider::refresh() {
stopAnimation(); stopAnimation();
if (_activeIndex >= _sections.size()) { if (_activeIndex >= _sections.size()) {
_activeIndex = 0; _activeIndex = 0;
@ -182,6 +205,13 @@ DiscreteSlider::Section::Section(
: label(st, label) { : label(st, label) {
} }
DiscreteSlider::Section::Section(
const TextWithEntities &label,
const style::TextStyle &st,
const std::any &context) {
this->label.setMarkedText(st, label, kMarkupTextOptions, context);
}
SettingsSlider::SettingsSlider( SettingsSlider::SettingsSlider(
QWidget *parent, QWidget *parent,
const style::SettingsSlider &st) const style::SettingsSlider &st)

View file

@ -22,7 +22,13 @@ public:
~DiscreteSlider(); ~DiscreteSlider();
void addSection(const QString &label); void addSection(const QString &label);
void addSection(
const TextWithEntities &label,
const std::any &context = {});
void setSections(const std::vector<QString> &labels); void setSections(const std::vector<QString> &labels);
void setSections(
const std::vector<TextWithEntities> &labels,
const std::any &context = {});
int activeSection() const { int activeSection() const {
return _activeIndex; return _activeIndex;
} }
@ -44,6 +50,10 @@ protected:
struct Section { struct Section {
Section(const QString &label, const style::TextStyle &st); Section(const QString &label, const style::TextStyle &st);
Section(
const TextWithEntities &label,
const style::TextStyle &st,
const std::any &context);
int left = 0; int left = 0;
int width = 0; int width = 0;
@ -75,6 +85,7 @@ protected:
_a_left.stop(); _a_left.stop();
_a_width.stop(); _a_width.stop();
} }
void refresh();
void setSelectOnPress(bool selectOnPress); void setSelectOnPress(bool selectOnPress);