mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Display emoji correctly in folder tags.
This commit is contained in:
parent
51b81dba87
commit
acfd92e2e6
8 changed files with 362 additions and 93 deletions
|
@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "chat_helpers/message_field.h"
|
#include "chat_helpers/message_field.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/core_settings.h"
|
#include "core/core_settings.h"
|
||||||
|
#include "core/ui_integration.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_chat_filters.h"
|
#include "data/data_chat_filters.h"
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
|
@ -443,6 +444,13 @@ void EditFilterBox(
|
||||||
nameEditing->settingDefault = false;
|
nameEditing->settingDefault = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const auto nameWithEntities = [=](bool upper = false) {
|
||||||
|
const auto entered = name->getTextWithTags();
|
||||||
|
return TextWithEntities{
|
||||||
|
(upper ? entered.text.toUpper() : entered.text),
|
||||||
|
TextUtilities::ConvertTextTagsToEntities(entered.tags),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const auto outer = box->getDelegate()->outerContainer();
|
const auto outer = box->getDelegate()->outerContainer();
|
||||||
CreateIconSelector(
|
CreateIconSelector(
|
||||||
|
@ -546,18 +554,28 @@ void EditFilterBox(
|
||||||
colors->width(),
|
colors->width(),
|
||||||
h);
|
h);
|
||||||
}, preview->lifetime());
|
}, preview->lifetime());
|
||||||
const auto previewTag = preview->lifetime().make_state<QImage>();
|
|
||||||
const auto previewAlpha = preview->lifetime().make_state<float64>(1);
|
struct TagState {
|
||||||
|
Ui::Animations::Simple animation;
|
||||||
|
Ui::ChatsFilterTagContext context;
|
||||||
|
QImage frame;
|
||||||
|
float64 alpha = 1.;
|
||||||
|
};
|
||||||
|
const auto tag = preview->lifetime().make_state<TagState>();
|
||||||
|
tag->context.textContext = Core::MarkedTextContext{
|
||||||
|
.session = session,
|
||||||
|
.customEmojiRepaint = [] {},
|
||||||
|
};
|
||||||
preview->paintRequest() | rpl::start_with_next([=] {
|
preview->paintRequest() | rpl::start_with_next([=] {
|
||||||
auto p = QPainter(preview);
|
auto p = QPainter(preview);
|
||||||
p.setOpacity(*previewAlpha);
|
p.setOpacity(tag->alpha);
|
||||||
const auto size = previewTag->size() / style::DevicePixelRatio();
|
const auto size = tag->frame.size() / style::DevicePixelRatio();
|
||||||
const auto rect = QRect(
|
const auto rect = QRect(
|
||||||
preview->width() - size.width() - st::boxRowPadding.right(),
|
preview->width() - size.width() - st::boxRowPadding.right(),
|
||||||
(st::normalFont->height - size.height()) / 2,
|
(st::normalFont->height - size.height()) / 2,
|
||||||
size.width(),
|
size.width(),
|
||||||
size.height());
|
size.height());
|
||||||
p.drawImage(rect.topLeft(), *previewTag);
|
p.drawImage(rect.topLeft(), tag->frame);
|
||||||
if (p.opacity() < 1) {
|
if (p.opacity() < 1) {
|
||||||
p.setOpacity(1. - p.opacity());
|
p.setOpacity(1. - p.opacity());
|
||||||
p.setFont(st::normalFont);
|
p.setFont(st::normalFont);
|
||||||
|
@ -574,16 +592,14 @@ void EditFilterBox(
|
||||||
Ui::CreateSkipWidget(colors, side),
|
Ui::CreateSkipWidget(colors, side),
|
||||||
st::boxRowPadding);
|
st::boxRowPadding);
|
||||||
auto buttons = std::vector<not_null<UserpicBuilder::CircleButton*>>();
|
auto buttons = std::vector<not_null<UserpicBuilder::CircleButton*>>();
|
||||||
const auto animation
|
|
||||||
= line->lifetime().make_state<Ui::Animations::Simple>();
|
|
||||||
const auto palette = [](int i) {
|
const auto palette = [](int i) {
|
||||||
return Ui::EmptyUserpic::UserpicColor(i).color2;
|
return Ui::EmptyUserpic::UserpicColor(i).color2;
|
||||||
};
|
};
|
||||||
name->changes() | rpl::start_with_next([=] {
|
name->changes() | rpl::start_with_next([=] {
|
||||||
*previewTag = Ui::ChatsFilterTag(
|
tag->context.color = palette(state->colorIndex.current())->c;
|
||||||
name->getLastText().toUpper(),
|
tag->frame = Ui::ChatsFilterTag(
|
||||||
palette(state->colorIndex.current())->c,
|
nameWithEntities(true),
|
||||||
false);
|
tag->context);
|
||||||
preview->update();
|
preview->update();
|
||||||
}, preview->lifetime());
|
}, preview->lifetime());
|
||||||
for (auto i = 0; i < kColorsCount; ++i) {
|
for (auto i = 0; i < kColorsCount; ++i) {
|
||||||
|
@ -597,12 +613,12 @@ void EditFilterBox(
|
||||||
const auto color = palette(i);
|
const auto color = palette(i);
|
||||||
button->setBrush(color);
|
button->setBrush(color);
|
||||||
if (progress == 1) {
|
if (progress == 1) {
|
||||||
*previewTag = Ui::ChatsFilterTag(
|
tag->context.color = color->c;
|
||||||
name->getLastText().toUpper(),
|
tag->frame = Ui::ChatsFilterTag(
|
||||||
color->c,
|
nameWithEntities(true),
|
||||||
false);
|
tag->context);
|
||||||
if (i == kNoTag) {
|
if (i == kNoTag) {
|
||||||
*previewAlpha = 0.;
|
tag->alpha = 0.;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buttons.push_back(button);
|
buttons.push_back(button);
|
||||||
|
@ -617,17 +633,17 @@ void EditFilterBox(
|
||||||
const auto c2 = palette(now);
|
const auto c2 = palette(now);
|
||||||
const auto a1 = (was == kNoTag) ? 0. : 1.;
|
const auto a1 = (was == kNoTag) ? 0. : 1.;
|
||||||
const auto a2 = (now == kNoTag) ? 0. : 1.;
|
const auto a2 = (now == kNoTag) ? 0. : 1.;
|
||||||
animation->stop();
|
tag->animation.stop();
|
||||||
animation->start([=](float64 progress) {
|
tag->animation.start([=](float64 progress) {
|
||||||
if (was >= 0) {
|
if (was >= 0) {
|
||||||
buttons[was]->setSelectedProgress(1. - progress);
|
buttons[was]->setSelectedProgress(1. - progress);
|
||||||
}
|
}
|
||||||
buttons[now]->setSelectedProgress(progress);
|
buttons[now]->setSelectedProgress(progress);
|
||||||
*previewTag = Ui::ChatsFilterTag(
|
tag->context.color = anim::color(c1, c2, progress);
|
||||||
name->getLastText().toUpper(),
|
tag->frame = Ui::ChatsFilterTag(
|
||||||
anim::color(c1, c2, progress),
|
nameWithEntities(true),
|
||||||
false);
|
tag->context);
|
||||||
*previewAlpha = anim::interpolateF(a1, a2, progress);
|
tag->alpha = anim::interpolateF(a1, a2, progress);
|
||||||
preview->update();
|
preview->update();
|
||||||
}, 0., 1., st::universalDuration);
|
}, 0., 1., st::universalDuration);
|
||||||
}
|
}
|
||||||
|
@ -673,11 +689,7 @@ void EditFilterBox(
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto collect = [=]() -> std::optional<Data::ChatFilter> {
|
const auto collect = [=]() -> std::optional<Data::ChatFilter> {
|
||||||
const auto entered = name->getTextWithTags();
|
const auto title = nameWithEntities();
|
||||||
const auto title = TextWithEntities{
|
|
||||||
entered.text,
|
|
||||||
TextUtilities::ConvertTextTagsToEntities(entered.tags),
|
|
||||||
};
|
|
||||||
const auto rules = data->current();
|
const auto rules = data->current();
|
||||||
if (title.empty()) {
|
if (title.empty()) {
|
||||||
name->showError();
|
name->showError();
|
||||||
|
|
|
@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ffmpeg/ffmpeg_frame_generator.h"
|
#include "ffmpeg/ffmpeg_frame_generator.h"
|
||||||
#include "chat_helpers/stickers_lottie.h"
|
#include "chat_helpers/stickers_lottie.h"
|
||||||
#include "storage/file_download.h" // kMaxFileInMemory
|
#include "storage/file_download.h" // kMaxFileInMemory
|
||||||
|
#include "ui/chat/chats_filter_tag.h"
|
||||||
#include "ui/effects/credits_graphics.h"
|
#include "ui/effects/credits_graphics.h"
|
||||||
#include "ui/widgets/fields/input_field.h"
|
#include "ui/widgets/fields/input_field.h"
|
||||||
#include "ui/text/custom_emoji_instance.h"
|
#include "ui/text/custom_emoji_instance.h"
|
||||||
|
@ -104,6 +105,14 @@ private:
|
||||||
return u"userpic:"_q;
|
return u"userpic:"_q;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QString ScaledSimplePrefix() {
|
||||||
|
return u"scaled-simple:"_q;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QString ScaledCustomPrefix() {
|
||||||
|
return u"scaled-custom:"_q;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] QString InternalPadding(QMargins value) {
|
[[nodiscard]] QString InternalPadding(QMargins value) {
|
||||||
return value.isNull() ? QString() : QString(",%1,%2,%3,%4"
|
return value.isNull() ? QString() : QString(",%1,%2,%3,%4"
|
||||||
).arg(value.left()
|
).arg(value.left()
|
||||||
|
@ -536,7 +545,16 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
||||||
Fn<void()> update,
|
Fn<void()> update,
|
||||||
SizeTag tag,
|
SizeTag tag,
|
||||||
int sizeOverride) {
|
int sizeOverride) {
|
||||||
if (data.startsWith(InternalPrefix())) {
|
if (data.startsWith(ScaledSimplePrefix())) {
|
||||||
|
const auto text = data.mid(ScaledSimplePrefix().size());
|
||||||
|
const auto emoji = Ui::Emoji::Find(text);
|
||||||
|
Assert(emoji != nullptr);
|
||||||
|
return Ui::MakeScaledSimpleEmoji(emoji);
|
||||||
|
} else if (data.startsWith(ScaledCustomPrefix())) {
|
||||||
|
const auto original = data.mid(ScaledCustomPrefix().size());
|
||||||
|
return Ui::MakeScaledCustomEmoji(
|
||||||
|
create(original, std::move(update), SizeTag::Large));
|
||||||
|
} else if (data.startsWith(InternalPrefix())) {
|
||||||
return internal(data);
|
return internal(data);
|
||||||
} else if (data.startsWith(UserpicEmojiPrefix())) {
|
} else if (data.startsWith(UserpicEmojiPrefix())) {
|
||||||
const auto ratio = style::DevicePixelRatio();
|
const auto ratio = style::DevicePixelRatio();
|
||||||
|
|
|
@ -112,8 +112,10 @@ taggedForumDialogRow: DialogRow(forumDialogRow) {
|
||||||
height: 96px;
|
height: 96px;
|
||||||
tagTop: 77px;
|
tagTop: 77px;
|
||||||
}
|
}
|
||||||
dialogRowFilterTagSkip : 4px;
|
dialogRowFilterTagSkip: 4px;
|
||||||
dialogRowFilterTagFont : font(10px);
|
dialogRowFilterTagStyle: TextStyle(defaultTextStyle) {
|
||||||
|
font: font(10px);
|
||||||
|
}
|
||||||
dialogRowOpenBotTextStyle: semiboldTextStyle;
|
dialogRowOpenBotTextStyle: semiboldTextStyle;
|
||||||
dialogRowOpenBotHeight: 20px;
|
dialogRowOpenBotHeight: 20px;
|
||||||
dialogRowOpenBotRight: 10px;
|
dialogRowOpenBotRight: 10px;
|
||||||
|
|
|
@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/click_handler_types.h"
|
#include "core/click_handler_types.h"
|
||||||
#include "core/shortcuts.h"
|
#include "core/shortcuts.h"
|
||||||
|
#include "core/ui_integration.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/widgets/scroll_area.h"
|
#include "ui/widgets/scroll_area.h"
|
||||||
|
@ -220,6 +221,11 @@ struct InnerWidget::PeerSearchResult {
|
||||||
BasicRow row;
|
BasicRow row;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct InnerWidget::TagCache {
|
||||||
|
Ui::ChatsFilterTagContext context;
|
||||||
|
QImage frame;
|
||||||
|
};
|
||||||
|
|
||||||
Key InnerWidget::FilterResult::key() const {
|
Key InnerWidget::FilterResult::key() const {
|
||||||
return row->key();
|
return row->key();
|
||||||
}
|
}
|
||||||
|
@ -4161,32 +4167,41 @@ QImage *InnerWidget::cacheChatsFilterTag(
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
const auto key = SerializeFilterTagsKey(filter.id(), more, active);
|
const auto key = SerializeFilterTagsKey(filter.id(), more, active);
|
||||||
{
|
auto &entry = _chatsFilterTags[key];
|
||||||
const auto it = _chatsFilterTags.find(key);
|
if (!entry.frame.isNull()) {
|
||||||
if (it != end(_chatsFilterTags)) {
|
if (!entry.context.loading) {
|
||||||
return &it->second;
|
return &entry.frame;
|
||||||
|
}
|
||||||
|
for (const auto &[k, emoji] : entry.context.emoji) {
|
||||||
|
if (!emoji->ready()) {
|
||||||
|
return &entry.frame; // Still waiting for emoji.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto roundedText = QString();
|
auto roundedText = TextWithEntities();
|
||||||
auto colorIndex = -1;
|
auto colorIndex = -1;
|
||||||
if (filter.id()) {
|
if (filter.id()) {
|
||||||
roundedText = filter.title().text.toUpper(); // todo filter emoji
|
roundedText = filter.title();
|
||||||
|
roundedText.text = roundedText.text.toUpper();
|
||||||
if (filter.colorIndex()) {
|
if (filter.colorIndex()) {
|
||||||
colorIndex = *(filter.colorIndex());
|
colorIndex = *(filter.colorIndex());
|
||||||
}
|
}
|
||||||
} else if (more > 0) {
|
} else if (more > 0) {
|
||||||
roundedText = QChar('+') + QString::number(more);
|
roundedText.text = QChar('+') + QString::number(more);
|
||||||
colorIndex = st::colorIndexBlue;
|
colorIndex = st::colorIndexBlue;
|
||||||
}
|
}
|
||||||
if (roundedText.isEmpty() || colorIndex < 0) {
|
if (roundedText.empty() || colorIndex < 0) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return &_chatsFilterTags.emplace(
|
const auto color = Ui::EmptyUserpic::UserpicColor(colorIndex).color2;
|
||||||
key,
|
entry.context.color = color->c;
|
||||||
Ui::ChatsFilterTag(
|
entry.context.active = active;
|
||||||
std::move(roundedText),
|
entry.context.textContext = Core::MarkedTextContext{
|
||||||
Ui::EmptyUserpic::UserpicColor(colorIndex).color2->c,
|
.session = &session(),
|
||||||
active)).first->second;
|
.customEmojiRepaint = [] {},
|
||||||
|
};
|
||||||
|
entry.frame = Ui::ChatsFilterTag(roundedText, entry.context);
|
||||||
|
return &entry.frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InnerWidget::chooseHashtag() {
|
bool InnerWidget::chooseHashtag() {
|
||||||
|
|
|
@ -221,6 +221,7 @@ private:
|
||||||
struct CollapsedRow;
|
struct CollapsedRow;
|
||||||
struct HashtagResult;
|
struct HashtagResult;
|
||||||
struct PeerSearchResult;
|
struct PeerSearchResult;
|
||||||
|
struct TagCache;
|
||||||
|
|
||||||
enum class JumpSkip {
|
enum class JumpSkip {
|
||||||
PreviousOrBegin,
|
PreviousOrBegin,
|
||||||
|
@ -579,7 +580,7 @@ private:
|
||||||
|
|
||||||
base::flat_map<FilterId, int> _chatsFilterScrollStates;
|
base::flat_map<FilterId, int> _chatsFilterScrollStates;
|
||||||
|
|
||||||
std::unordered_map<ChatsFilterTagsKey, QImage> _chatsFilterTags;
|
std::unordered_map<ChatsFilterTagsKey, TagCache> _chatsFilterTags;
|
||||||
bool _waitingAllChatListEntryRefreshesForTags = false;
|
bool _waitingAllChatListEntryRefreshesForTags = false;
|
||||||
rpl::lifetime _handleChatListEntryTagRefreshesLifetime;
|
rpl::lifetime _handleChatListEntryTagRefreshesLifetime;
|
||||||
|
|
||||||
|
|
|
@ -7,59 +7,246 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "ui/chat/chats_filter_tag.h"
|
#include "ui/chat/chats_filter_tag.h"
|
||||||
|
|
||||||
|
#include "ui/text/text_custom_emoji.h"
|
||||||
#include "ui/emoji_config.h"
|
#include "ui/emoji_config.h"
|
||||||
|
#include "ui/integration.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "styles/style_dialogs.h"
|
#include "styles/style_dialogs.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
QImage ChatsFilterTag(QString roundedText, QColor color, bool active) {
|
class ScaledSimpleEmoji final : public Ui::Text::CustomEmoji {
|
||||||
const auto &roundedFont = st::dialogRowFilterTagFont;
|
public:
|
||||||
const auto additionalWidth = roundedFont->spacew * 3;
|
ScaledSimpleEmoji(EmojiPtr emoji);
|
||||||
struct EmojiReplacement final {
|
|
||||||
QPixmap pixmap;
|
int width() override;
|
||||||
int from = -1;
|
QString entityData() override;
|
||||||
int length = 0;
|
void paint(QPainter &p, const Context &context) override;
|
||||||
float64 x = -1;
|
void unload() override;
|
||||||
|
bool ready() override;
|
||||||
|
bool readyInDefaultState() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const EmojiPtr _emoji;
|
||||||
|
QImage _frame;
|
||||||
|
QPoint _shift;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScaledCustomEmoji final : public Ui::Text::CustomEmoji {
|
||||||
|
public:
|
||||||
|
ScaledCustomEmoji(std::unique_ptr<Ui::Text::CustomEmoji> wrapped);
|
||||||
|
|
||||||
|
int width() override;
|
||||||
|
QString entityData() override;
|
||||||
|
void paint(QPainter &p, const Context &context) override;
|
||||||
|
void unload() override;
|
||||||
|
bool ready() override;
|
||||||
|
bool readyInDefaultState() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::unique_ptr<Ui::Text::CustomEmoji> _wrapped;
|
||||||
|
QImage _frame;
|
||||||
|
QPoint _shift;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] int ScaledSize() {
|
||||||
|
return st::dialogRowFilterTagStyle.font->height - 2 * st::lineWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScaledSimpleEmoji::ScaledSimpleEmoji(EmojiPtr emoji)
|
||||||
|
: _emoji(emoji) {
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScaledSimpleEmoji::width() {
|
||||||
|
return ScaledSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ScaledSimpleEmoji::entityData() {
|
||||||
|
return u"scaled-simple:"_q + _emoji->text();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScaledSimpleEmoji::paint(QPainter &p, const Context &context) {
|
||||||
|
if (_frame.isNull()) {
|
||||||
|
const auto adjusted = Text::AdjustCustomEmojiSize(st::emojiSize);
|
||||||
|
const auto xskip = (st::emojiSize - adjusted) / 2;
|
||||||
|
const auto yskip = xskip + (width() - st::emojiSize) / 2;
|
||||||
|
_shift = { xskip, yskip };
|
||||||
|
|
||||||
|
const auto ratio = style::DevicePixelRatio();
|
||||||
|
const auto large = Emoji::GetSizeLarge();
|
||||||
|
const auto size = QSize(large, large);
|
||||||
|
_frame = QImage(size, QImage::Format_ARGB32_Premultiplied);
|
||||||
|
_frame.setDevicePixelRatio(ratio);
|
||||||
|
_frame.fill(Qt::transparent);
|
||||||
|
|
||||||
|
auto p = QPainter(&_frame);
|
||||||
|
Emoji::Draw(p, _emoji, large, 0, 0);
|
||||||
|
p.end();
|
||||||
|
|
||||||
|
_frame = _frame.scaled(
|
||||||
|
QSize(width(), width()) * ratio,
|
||||||
|
Qt::IgnoreAspectRatio,
|
||||||
|
Qt::SmoothTransformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
p.drawImage(context.position - _shift, _frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScaledSimpleEmoji::unload() {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScaledSimpleEmoji::ready() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScaledSimpleEmoji::readyInDefaultState() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScaledCustomEmoji::ScaledCustomEmoji(
|
||||||
|
std::unique_ptr<Ui::Text::CustomEmoji> wrapped)
|
||||||
|
: _wrapped(std::move(wrapped)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScaledCustomEmoji::width() {
|
||||||
|
return ScaledSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ScaledCustomEmoji::entityData() {
|
||||||
|
return u"scaled-custom:"_q + _wrapped->entityData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScaledCustomEmoji::paint(QPainter &p, const Context &context) {
|
||||||
|
if (_frame.isNull()) {
|
||||||
|
if (!_wrapped->ready()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto ratio = style::DevicePixelRatio();
|
||||||
|
const auto large = Emoji::GetSizeLarge();
|
||||||
|
const auto largeadjust = Text::AdjustCustomEmojiSize(large / ratio);
|
||||||
|
const auto size = QSize(largeadjust, largeadjust) * ratio;
|
||||||
|
_frame = QImage(size, QImage::Format_ARGB32_Premultiplied);
|
||||||
|
_frame.setDevicePixelRatio(ratio);
|
||||||
|
_frame.fill(Qt::transparent);
|
||||||
|
|
||||||
|
auto p = QPainter(&_frame);
|
||||||
|
p.translate(-context.position);
|
||||||
|
const auto was = context.internal.forceFirstFrame;
|
||||||
|
context.internal.forceFirstFrame = true;
|
||||||
|
_wrapped->paint(p, context);
|
||||||
|
context.internal.forceFirstFrame = was;
|
||||||
|
p.end();
|
||||||
|
|
||||||
|
const auto smalladjust = Text::AdjustCustomEmojiSize(width());
|
||||||
|
_frame = _frame.scaled(
|
||||||
|
QSize(smalladjust, smalladjust) * ratio,
|
||||||
|
Qt::IgnoreAspectRatio,
|
||||||
|
Qt::SmoothTransformation);
|
||||||
|
_wrapped->unload();
|
||||||
|
|
||||||
|
const auto adjusted = Text::AdjustCustomEmojiSize(st::emojiSize);
|
||||||
|
const auto xskip = (st::emojiSize - adjusted) / 2;
|
||||||
|
const auto yskip = xskip + (width() - st::emojiSize) / 2;
|
||||||
|
|
||||||
|
const auto add = (width() - smalladjust) / 2;
|
||||||
|
_shift = QPoint(xskip, yskip) - QPoint(add, add);
|
||||||
|
}
|
||||||
|
p.drawImage(context.position - _shift, _frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScaledCustomEmoji::unload() {
|
||||||
|
_wrapped->unload();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScaledCustomEmoji::ready() {
|
||||||
|
return !_frame.isNull() || _wrapped->ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScaledCustomEmoji::readyInDefaultState() {
|
||||||
|
return !_frame.isNull() || _wrapped->ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] TextWithEntities PrepareSmallEmojiText(
|
||||||
|
TextWithEntities text,
|
||||||
|
ChatsFilterTagContext &context) {
|
||||||
|
auto i = text.entities.begin();
|
||||||
|
auto ch = text.text.constData();
|
||||||
|
auto &integration = Integration::Instance();
|
||||||
|
context.loading = false;
|
||||||
|
const auto end = text.text.constData() + text.text.size();
|
||||||
|
const auto adjust = [&](EntityInText &entity) {
|
||||||
|
if (entity.type() != EntityType::CustomEmoji) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto data = entity.data();
|
||||||
|
if (data.startsWith(u"scaled-simple:"_q)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto &emoji = context.emoji[data];
|
||||||
|
if (!emoji) {
|
||||||
|
emoji = integration.createCustomEmoji(
|
||||||
|
data,
|
||||||
|
context.textContext);
|
||||||
|
}
|
||||||
|
if (!emoji->ready()) {
|
||||||
|
context.loading = true;
|
||||||
|
}
|
||||||
|
entity = EntityInText(
|
||||||
|
entity.type(),
|
||||||
|
entity.offset(),
|
||||||
|
entity.length(),
|
||||||
|
u"scaled-custom:"_q + entity.data());
|
||||||
|
};
|
||||||
|
const auto till = [](EntityInText &entity) {
|
||||||
|
return entity.offset() + entity.length();
|
||||||
};
|
};
|
||||||
auto emojiReplacements = std::vector<EmojiReplacement>();
|
|
||||||
auto ch = roundedText.constData();
|
|
||||||
const auto end = ch + roundedText.size();
|
|
||||||
while (ch != end) {
|
while (ch != end) {
|
||||||
auto emojiLength = 0;
|
auto emojiLength = 0;
|
||||||
if (const auto emoji = Ui::Emoji::Find(ch, end, &emojiLength)) {
|
if (const auto emoji = Ui::Emoji::Find(ch, end, &emojiLength)) {
|
||||||
const auto factor = style::DevicePixelRatio();
|
const auto f = int(ch - text.text.constData());
|
||||||
emojiReplacements.push_back({
|
const auto l = f + emojiLength;
|
||||||
.pixmap = Ui::Emoji::SinglePixmap(
|
while (i != text.entities.end() && till(*i) <= f) {
|
||||||
emoji,
|
adjust(*i);
|
||||||
st::normalFont->height * factor).scaledToHeight(
|
++i;
|
||||||
roundedFont->ascent * factor,
|
}
|
||||||
Qt::SmoothTransformation),
|
|
||||||
.from = int(ch - roundedText.constData()),
|
|
||||||
.length = emojiLength,
|
|
||||||
});
|
|
||||||
ch += emojiLength;
|
ch += emojiLength;
|
||||||
|
if (i != text.entities.end() && i->offset() < l) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i = text.entities.insert(i, EntityInText{
|
||||||
|
EntityType::CustomEmoji,
|
||||||
|
f,
|
||||||
|
emojiLength,
|
||||||
|
u"scaled-simple:"_q + emoji->text(),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
ch++;
|
++ch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!emojiReplacements.empty()) {
|
for (; i != text.entities.end(); ++i) {
|
||||||
auto addedChars = 0;
|
adjust(*i);
|
||||||
for (auto &e : emojiReplacements) {
|
|
||||||
const auto pixmapWidth = e.pixmap.width()
|
|
||||||
/ style::DevicePixelRatio();
|
|
||||||
const auto spaces = 1 + pixmapWidth / roundedFont->spacew;
|
|
||||||
const auto placeholder = QString(spaces, ' ');
|
|
||||||
const auto from = e.from + addedChars;
|
|
||||||
e.x = roundedFont->width(roundedText.mid(0, from))
|
|
||||||
+ additionalWidth / 2.
|
|
||||||
+ (roundedFont->width(placeholder) - pixmapWidth) / 2.;
|
|
||||||
roundedText.replace(from, e.length, placeholder);
|
|
||||||
addedChars += spaces - e.length;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const auto roundedWidth = roundedFont->width(roundedText)
|
return text;
|
||||||
+ additionalWidth;
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
QImage ChatsFilterTag(
|
||||||
|
const TextWithEntities &text,
|
||||||
|
ChatsFilterTagContext &context) {
|
||||||
|
const auto &roundedFont = st::dialogRowFilterTagStyle.font;
|
||||||
|
const auto additionalWidth = roundedFont->spacew * 3;
|
||||||
|
auto rich = Text::String(
|
||||||
|
st::dialogRowFilterTagStyle,
|
||||||
|
PrepareSmallEmojiText(text, context),
|
||||||
|
kMarkupTextOptions,
|
||||||
|
kQFixedMax,
|
||||||
|
context.textContext);
|
||||||
|
const auto roundedWidth = rich.maxWidth() + additionalWidth;
|
||||||
const auto rect = QRect(0, 0, roundedWidth, roundedFont->height);
|
const auto rect = QRect(0, 0, roundedWidth, roundedFont->height);
|
||||||
auto cache = QImage(
|
auto cache = QImage(
|
||||||
rect.size() * style::DevicePixelRatio(),
|
rect.size() * style::DevicePixelRatio(),
|
||||||
|
@ -68,9 +255,11 @@ QImage ChatsFilterTag(QString roundedText, QColor color, bool active) {
|
||||||
cache.fill(Qt::transparent);
|
cache.fill(Qt::transparent);
|
||||||
{
|
{
|
||||||
auto p = QPainter(&cache);
|
auto p = QPainter(&cache);
|
||||||
const auto pen = QPen(active ? st::dialogsBgActive->c : color);
|
const auto pen = QPen(context.active
|
||||||
|
? st::dialogsBgActive->c
|
||||||
|
: context.color);
|
||||||
p.setPen(Qt::NoPen);
|
p.setPen(Qt::NoPen);
|
||||||
p.setBrush(active
|
p.setBrush(context.active
|
||||||
? st::dialogsTextFgActive->c
|
? st::dialogsTextFgActive->c
|
||||||
: anim::with_alpha(pen.color(), .15));
|
: anim::with_alpha(pen.color(), .15));
|
||||||
{
|
{
|
||||||
|
@ -80,13 +269,23 @@ QImage ChatsFilterTag(QString roundedText, QColor color, bool active) {
|
||||||
}
|
}
|
||||||
p.setPen(pen);
|
p.setPen(pen);
|
||||||
p.setFont(roundedFont);
|
p.setFont(roundedFont);
|
||||||
p.drawText(rect, roundedText, style::al_center);
|
const auto dx = (rect.width() - rich.maxWidth()) / 2;
|
||||||
for (const auto &e : emojiReplacements) {
|
const auto dy = (rect.height() - roundedFont->height) / 2;
|
||||||
const auto h = e.pixmap.height() / style::DevicePixelRatio();
|
rich.draw(p, {
|
||||||
p.drawPixmap(QPointF(e.x, (rect.height() - h) / 2), e.pixmap);
|
.position = rect.topLeft() + QPoint(dx, dy),
|
||||||
}
|
.availableWidth = rich.maxWidth(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Text::CustomEmoji> MakeScaledSimpleEmoji(EmojiPtr emoji) {
|
||||||
|
return std::make_unique<ScaledSimpleEmoji>(emoji);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Text::CustomEmoji> MakeScaledCustomEmoji(
|
||||||
|
std::unique_ptr<Text::CustomEmoji> wrapped) {
|
||||||
|
return std::make_unique<ScaledCustomEmoji>(std::move(wrapped));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
@ -7,8 +7,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "emoji.h"
|
||||||
|
|
||||||
|
namespace Ui::Text {
|
||||||
|
class CustomEmoji;
|
||||||
|
} // namespace Ui::Text
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
|
||||||
[[nodiscard]] QImage ChatsFilterTag(QString text, QColor color, bool active);
|
struct ChatsFilterTagContext {
|
||||||
|
base::flat_map<QString, std::unique_ptr<Text::CustomEmoji>> emoji;
|
||||||
|
std::any textContext;
|
||||||
|
QColor color;
|
||||||
|
bool active = false;
|
||||||
|
bool loading = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] QImage ChatsFilterTag(
|
||||||
|
const TextWithEntities &text,
|
||||||
|
ChatsFilterTagContext &context);
|
||||||
|
|
||||||
|
[[nodiscard]] std::unique_ptr<Text::CustomEmoji> MakeScaledSimpleEmoji(
|
||||||
|
EmojiPtr emoji);
|
||||||
|
|
||||||
|
[[nodiscard]] std::unique_ptr<Text::CustomEmoji> MakeScaledCustomEmoji(
|
||||||
|
std::unique_ptr<Text::CustomEmoji> wrapped);
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit c1ea8aef5073785ce6e35db9eda830604f81ed62
|
Subproject commit 8cb06a75d981d7a1a2f2a5df420ef20ff4c0b097
|
Loading…
Add table
Reference in a new issue