mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Support suggestions of custom emoji.
This commit is contained in:
parent
bf286cf175
commit
04d4fdbf9b
5 changed files with 271 additions and 143 deletions
|
@ -38,81 +38,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace ChatHelpers {
|
namespace ChatHelpers {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kFakeEmojiDocumentIdBase = 0x7777'FFFF'FFFF'0000ULL;
|
|
||||||
|
|
||||||
using Core::RecentEmojiId;
|
using Core::RecentEmojiId;
|
||||||
using Core::RecentEmojiDocument;
|
using Core::RecentEmojiDocument;
|
||||||
|
|
||||||
[[nodiscard]] DocumentId FakeEmojiDocumentId(EmojiPtr emoji) {
|
|
||||||
return kFakeEmojiDocumentIdBase + emoji->index();
|
|
||||||
}
|
|
||||||
|
|
||||||
class DefaultEmojiLoader final : public Ui::CustomEmoji::Loader {
|
|
||||||
public:
|
|
||||||
DefaultEmojiLoader(EmojiPtr emoji, int size);
|
|
||||||
|
|
||||||
QString entityData() override;
|
|
||||||
|
|
||||||
void load(Fn<void(LoadResult)> loaded) override;
|
|
||||||
bool loading() override;
|
|
||||||
void cancel() override;
|
|
||||||
Ui::CustomEmoji::Preview preview() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void validateImage();
|
|
||||||
|
|
||||||
EmojiPtr _emoji = nullptr;
|
|
||||||
QImage _image;
|
|
||||||
int _size = 0;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
DefaultEmojiLoader::DefaultEmojiLoader(EmojiPtr emoji, int size)
|
|
||||||
: _emoji(emoji)
|
|
||||||
, _size(size) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void DefaultEmojiLoader::load(Fn<void(LoadResult)> loaded) {
|
|
||||||
validateImage();
|
|
||||||
const auto data = entityData();
|
|
||||||
const auto unloader = [emoji = _emoji, size = _size] {
|
|
||||||
return std::make_unique<DefaultEmojiLoader>(emoji, size);
|
|
||||||
};
|
|
||||||
auto cache = Ui::CustomEmoji::Cache(_size);
|
|
||||||
cache.add(0, _image);
|
|
||||||
cache.finish();
|
|
||||||
loaded(Ui::CustomEmoji::Cached(data, unloader, std::move(cache)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void DefaultEmojiLoader::validateImage() {
|
|
||||||
if (!_image.isNull()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_image = QImage(
|
|
||||||
{ _size, _size },
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
_image.setDevicePixelRatio(style::DevicePixelRatio());
|
|
||||||
_image.fill(Qt::transparent);
|
|
||||||
QPainter p(&_image);
|
|
||||||
Ui::Emoji::Draw(p, _emoji, _size, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString DefaultEmojiLoader::entityData() {
|
|
||||||
return "default-emoji://" + _emoji->id();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DefaultEmojiLoader::loading() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DefaultEmojiLoader::cancel() {
|
|
||||||
}
|
|
||||||
|
|
||||||
Ui::CustomEmoji::Preview DefaultEmojiLoader::preview() {
|
|
||||||
validateImage();
|
|
||||||
return { _image };
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
class EmojiColorPicker : public Ui::RpWidget {
|
class EmojiColorPicker : public Ui::RpWidget {
|
||||||
|
@ -181,7 +109,7 @@ struct EmojiListWidget::CustomInstance {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EmojiListWidget::RecentOne {
|
struct EmojiListWidget::RecentOne {
|
||||||
not_null<CustomInstance*> instance;
|
CustomInstance *instance = nullptr;
|
||||||
RecentEmojiId id;
|
RecentEmojiId id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -881,7 +809,7 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
|
||||||
if (info.section == int(Section::Recent)) {
|
if (info.section == int(Section::Recent)) {
|
||||||
drawRecent(p, w, now, paused, index);
|
drawRecent(p, w, now, paused, index);
|
||||||
} else if (info.section < kEmojiSectionCount) {
|
} else if (info.section < kEmojiSectionCount) {
|
||||||
drawEmoji(p, w, info.section, index);
|
drawEmoji(p, w, _emoji[info.section][index]);
|
||||||
} else {
|
} else {
|
||||||
const auto set = info.section - kEmojiSectionCount;
|
const auto set = info.section - kEmojiSectionCount;
|
||||||
drawCustom(p, w, now, paused, set, index);
|
drawCustom(p, w, now, paused, set, index);
|
||||||
|
@ -919,24 +847,29 @@ void EmojiListWidget::drawRecent(
|
||||||
int index) {
|
int index) {
|
||||||
const auto size = (_esize / cIntRetinaFactor());
|
const auto size = (_esize / cIntRetinaFactor());
|
||||||
_recentPainted = true;
|
_recentPainted = true;
|
||||||
_recent[index].instance->object.paint(
|
if (const auto emoji = std::get_if<EmojiPtr>(&_recent[index].id.data)) {
|
||||||
p,
|
drawEmoji(p, position, *emoji);
|
||||||
position.x() + (_singleSize.width() - size) / 2,
|
} else {
|
||||||
position.y() + (_singleSize.height() - size) / 2,
|
Assert(_recent[index].instance != nullptr);
|
||||||
now,
|
|
||||||
st::windowBgRipple->c,
|
_recent[index].instance->object.paint(
|
||||||
paused);
|
p,
|
||||||
|
position.x() + (_singleSize.width() - size) / 2,
|
||||||
|
position.y() + (_singleSize.height() - size) / 2,
|
||||||
|
now,
|
||||||
|
st::windowBgRipple->c,
|
||||||
|
paused);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmojiListWidget::drawEmoji(
|
void EmojiListWidget::drawEmoji(
|
||||||
QPainter &p,
|
QPainter &p,
|
||||||
QPoint position,
|
QPoint position,
|
||||||
int section,
|
EmojiPtr emoji) {
|
||||||
int index) {
|
|
||||||
const auto size = (_esize / cIntRetinaFactor());
|
const auto size = (_esize / cIntRetinaFactor());
|
||||||
Ui::Emoji::Draw(
|
Ui::Emoji::Draw(
|
||||||
p,
|
p,
|
||||||
_emoji[section][index],
|
emoji,
|
||||||
_esize,
|
_esize,
|
||||||
position.x() + (_singleSize.width() - size) / 2,
|
position.x() + (_singleSize.width() - size) / 2,
|
||||||
position.y() + (_singleSize.height() - size) / 2);
|
position.y() + (_singleSize.height() - size) / 2);
|
||||||
|
@ -1385,7 +1318,7 @@ auto EmojiListWidget::resolveCustomInstance(
|
||||||
setId);
|
setId);
|
||||||
if (recentOnly) {
|
if (recentOnly) {
|
||||||
for (auto &recent : _recent) {
|
for (auto &recent : _recent) {
|
||||||
if (recent.instance == i->second.get()) {
|
if (recent.instance && recent.instance == i->second.get()) {
|
||||||
recent.instance = instance.get();
|
recent.instance = instance.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1399,35 +1332,16 @@ auto EmojiListWidget::resolveCustomInstance(
|
||||||
|
|
||||||
auto EmojiListWidget::resolveCustomInstance(
|
auto EmojiListWidget::resolveCustomInstance(
|
||||||
RecentEmojiId customId)
|
RecentEmojiId customId)
|
||||||
-> not_null<CustomInstance*> {
|
-> CustomInstance* {
|
||||||
const auto &data = customId.data;
|
const auto &data = customId.data;
|
||||||
if (const auto document = std::get_if<RecentEmojiDocument>(&data)) {
|
if (const auto document = std::get_if<RecentEmojiDocument>(&data)) {
|
||||||
return resolveCustomInstance(document->id);
|
return resolveCustomInstance(document->id);
|
||||||
} else if (const auto emoji = std::get_if<EmojiPtr>(&data)) {
|
} else if (const auto emoji = std::get_if<EmojiPtr>(&data)) {
|
||||||
return resolveCustomInstance(FakeEmojiDocumentId(*emoji), *emoji);
|
return nullptr;
|
||||||
}
|
}
|
||||||
Unexpected("Custom recent emoji id.");
|
Unexpected("Custom recent emoji id.");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto EmojiListWidget::resolveCustomInstance(
|
|
||||||
DocumentId fakeId,
|
|
||||||
EmojiPtr emoji)
|
|
||||||
-> not_null<CustomInstance*> {
|
|
||||||
const auto i = _instances.find(fakeId);
|
|
||||||
if (i != end(_instances)) {
|
|
||||||
return i->second.get();
|
|
||||||
}
|
|
||||||
return _instances.emplace(
|
|
||||||
fakeId,
|
|
||||||
std::make_unique<CustomInstance>(
|
|
||||||
std::make_unique<DefaultEmojiLoader>(
|
|
||||||
emoji,
|
|
||||||
Ui::Emoji::GetSizeLarge()),
|
|
||||||
[](const auto&, const auto&) {},
|
|
||||||
[] {},
|
|
||||||
true)).first->second.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto EmojiListWidget::resolveCustomInstance(
|
auto EmojiListWidget::resolveCustomInstance(
|
||||||
DocumentId documentId)
|
DocumentId documentId)
|
||||||
-> not_null<CustomInstance*> {
|
-> not_null<CustomInstance*> {
|
||||||
|
|
|
@ -196,8 +196,7 @@ private:
|
||||||
void drawEmoji(
|
void drawEmoji(
|
||||||
QPainter &p,
|
QPainter &p,
|
||||||
QPoint position,
|
QPoint position,
|
||||||
int section,
|
EmojiPtr emoji);
|
||||||
int index);
|
|
||||||
void drawCustom(
|
void drawCustom(
|
||||||
QPainter &p,
|
QPainter &p,
|
||||||
QPoint position,
|
QPoint position,
|
||||||
|
@ -234,11 +233,8 @@ private:
|
||||||
[[nodiscard]] not_null<CustomInstance*> resolveCustomInstance(
|
[[nodiscard]] not_null<CustomInstance*> resolveCustomInstance(
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
uint64 setId);
|
uint64 setId);
|
||||||
[[nodiscard]] not_null<CustomInstance*> resolveCustomInstance(
|
[[nodiscard]] CustomInstance *resolveCustomInstance(
|
||||||
Core::RecentEmojiId customId);
|
Core::RecentEmojiId customId);
|
||||||
[[nodiscard]] not_null<CustomInstance*> resolveCustomInstance(
|
|
||||||
DocumentId fakeId,
|
|
||||||
EmojiPtr emoji);
|
|
||||||
[[nodiscard]] not_null<CustomInstance*> resolveCustomInstance(
|
[[nodiscard]] not_null<CustomInstance*> resolveCustomInstance(
|
||||||
DocumentId documentId);
|
DocumentId documentId);
|
||||||
[[nodiscard]] std::unique_ptr<CustomInstance> customInstanceWithLoader(
|
[[nodiscard]] std::unique_ptr<CustomInstance> customInstanceWithLoader(
|
||||||
|
|
|
@ -22,6 +22,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "base/event_filter.h"
|
#include "base/event_filter.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_document.h"
|
||||||
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
|
#include "data/stickers/data_stickers.h"
|
||||||
#include "styles/style_chat_helpers.h"
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
#include <QtWidgets/QApplication>
|
#include <QtWidgets/QApplication>
|
||||||
|
@ -37,8 +41,36 @@ constexpr auto kAnimationDuration = crl::time(120);
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
SuggestionsWidget::SuggestionsWidget(QWidget *parent)
|
struct SuggestionsWidget::CustomInstance {
|
||||||
|
CustomInstance(
|
||||||
|
std::unique_ptr<Ui::CustomEmoji::Loader> loader,
|
||||||
|
Fn<void(
|
||||||
|
not_null<Ui::CustomEmoji::Instance*>,
|
||||||
|
Ui::CustomEmoji::RepaintRequest)> repaintLater,
|
||||||
|
Fn<void()> repaint);
|
||||||
|
|
||||||
|
Ui::CustomEmoji::Instance emoji;
|
||||||
|
Ui::CustomEmoji::Object object;
|
||||||
|
};
|
||||||
|
|
||||||
|
SuggestionsWidget::CustomInstance::CustomInstance(
|
||||||
|
std::unique_ptr<Ui::CustomEmoji::Loader> loader,
|
||||||
|
Fn<void(
|
||||||
|
not_null<Ui::CustomEmoji::Instance*>,
|
||||||
|
Ui::CustomEmoji::RepaintRequest)> repaintLater,
|
||||||
|
Fn<void()> repaint)
|
||||||
|
: emoji(
|
||||||
|
Ui::CustomEmoji::Loading(std::move(loader), Ui::CustomEmoji::Preview()),
|
||||||
|
std::move(repaintLater))
|
||||||
|
, object(&emoji, std::move(repaint)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
SuggestionsWidget::SuggestionsWidget(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Main::Session*> session)
|
||||||
: RpWidget(parent)
|
: RpWidget(parent)
|
||||||
|
, _session(session)
|
||||||
|
, _repaintTimer([=] { invokeRepaints(); })
|
||||||
, _oneWidth(st::emojiSuggestionSize)
|
, _oneWidth(st::emojiSuggestionSize)
|
||||||
, _padding(st::emojiSuggestionsPadding) {
|
, _padding(st::emojiSuggestionsPadding) {
|
||||||
resize(
|
resize(
|
||||||
|
@ -47,11 +79,13 @@ SuggestionsWidget::SuggestionsWidget(QWidget *parent)
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SuggestionsWidget::~SuggestionsWidget() = default;
|
||||||
|
|
||||||
rpl::producer<bool> SuggestionsWidget::toggleAnimated() const {
|
rpl::producer<bool> SuggestionsWidget::toggleAnimated() const {
|
||||||
return _toggleAnimated.events();
|
return _toggleAnimated.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<QString> SuggestionsWidget::triggered() const {
|
auto SuggestionsWidget::triggered() const -> rpl::producer<Chosen> {
|
||||||
return _triggered.events();
|
return _triggered.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +94,7 @@ void SuggestionsWidget::showWithQuery(const QString &query, bool force) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_query = query;
|
_query = query;
|
||||||
auto rows = getRowsByQuery();
|
auto rows = prependCustom(getRowsByQuery());
|
||||||
if (rows.empty()) {
|
if (rows.empty()) {
|
||||||
_toggleAnimated.fire(false);
|
_toggleAnimated.fire(false);
|
||||||
}
|
}
|
||||||
|
@ -83,6 +117,142 @@ void SuggestionsWidget::selectFirstResult() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto SuggestionsWidget::prependCustom(std::vector<Row> rows)
|
||||||
|
-> std::vector<Row> {
|
||||||
|
if (rows.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
struct Custom {
|
||||||
|
not_null<DocumentData*> document;
|
||||||
|
not_null<EmojiPtr> emoji;
|
||||||
|
QString replacement;
|
||||||
|
};
|
||||||
|
auto custom = base::flat_multi_map<int, Custom>();
|
||||||
|
const auto premium = _session->premium();
|
||||||
|
const auto stickers = &_session->data().stickers();
|
||||||
|
for (const auto setId : stickers->emojiSetsOrder()) {
|
||||||
|
const auto i = stickers->sets().find(setId);
|
||||||
|
if (i == end(stickers->sets())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const auto &document : i->second->stickers) {
|
||||||
|
if (!premium && document->isPremiumEmoji()) {
|
||||||
|
// Skip the whole premium emoji set.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (const auto sticker = document->sticker()) {
|
||||||
|
if (const auto emoji = Ui::Emoji::Find(sticker->alt)) {
|
||||||
|
const auto j = ranges::find(
|
||||||
|
rows,
|
||||||
|
not_null{ emoji },
|
||||||
|
&Row::emoji);
|
||||||
|
if (j != end(rows)) {
|
||||||
|
custom.emplace(int(j - begin(rows)), Custom{
|
||||||
|
.document = document,
|
||||||
|
.emoji = j->emoji,
|
||||||
|
.replacement = j->replacement,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (custom.empty()) {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
auto result = std::vector<Row>();
|
||||||
|
result.reserve(custom.size() + rows.size());
|
||||||
|
for (const auto &[position, one] : custom) {
|
||||||
|
result.push_back(Row(one.emoji, one.replacement));
|
||||||
|
result.back().document = one.document;
|
||||||
|
result.back().instance = resolveCustomInstance(one.document);
|
||||||
|
}
|
||||||
|
for (auto &row : rows) {
|
||||||
|
result.push_back(std::move(row));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SuggestionsWidget::resolveCustomInstance(
|
||||||
|
not_null<DocumentData*> document)
|
||||||
|
-> not_null<CustomInstance*> {
|
||||||
|
const auto i = _instances.find(document);
|
||||||
|
if (i != end(_instances)) {
|
||||||
|
return i->second.get();
|
||||||
|
}
|
||||||
|
const auto repaintDelayed = [=](
|
||||||
|
not_null<Ui::CustomEmoji::Instance*> instance,
|
||||||
|
Ui::CustomEmoji::RepaintRequest request) {
|
||||||
|
if (_instances.empty() || !request.when) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto &when = _repaints[request.duration];
|
||||||
|
if (when < request.when) {
|
||||||
|
when = request.when;
|
||||||
|
}
|
||||||
|
if (_repaintTimerScheduled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scheduleRepaintTimer();
|
||||||
|
};
|
||||||
|
const auto repaintNow = [=] {
|
||||||
|
update();
|
||||||
|
};
|
||||||
|
auto instance = std::make_unique<CustomInstance>(
|
||||||
|
_session->data().customEmojiManager().createLoader(
|
||||||
|
document,
|
||||||
|
Data::CustomEmojiManager::SizeTag::Large),
|
||||||
|
std::move(repaintDelayed),
|
||||||
|
std::move(repaintNow));
|
||||||
|
return _instances.emplace(
|
||||||
|
document,
|
||||||
|
std::move(instance)
|
||||||
|
).first->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SuggestionsWidget::scheduleRepaintTimer() {
|
||||||
|
_repaintTimerScheduled = true;
|
||||||
|
Ui::PostponeCall(this, [=] {
|
||||||
|
_repaintTimerScheduled = false;
|
||||||
|
|
||||||
|
auto next = crl::time();
|
||||||
|
for (const auto &[duration, when] : _repaints) {
|
||||||
|
if (!next || next > when) {
|
||||||
|
next = when;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (next && (!_repaintNext || _repaintNext > next)) {
|
||||||
|
const auto now = crl::now();
|
||||||
|
if (now >= next) {
|
||||||
|
_repaintNext = 0;
|
||||||
|
_repaintTimer.cancel();
|
||||||
|
invokeRepaints();
|
||||||
|
} else {
|
||||||
|
_repaintNext = next;
|
||||||
|
_repaintTimer.callOnce(next - now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SuggestionsWidget::invokeRepaints() {
|
||||||
|
_repaintNext = 0;
|
||||||
|
auto invoke = false;
|
||||||
|
const auto now = crl::now();
|
||||||
|
for (auto i = begin(_repaints); i != end(_repaints);) {
|
||||||
|
if (i->second > now) {
|
||||||
|
++i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
invoke = true;
|
||||||
|
i = _repaints.erase(i);
|
||||||
|
}
|
||||||
|
if (invoke) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
scheduleRepaintTimer();
|
||||||
|
}
|
||||||
|
|
||||||
SuggestionsWidget::Row::Row(
|
SuggestionsWidget::Row::Row(
|
||||||
not_null<EmojiPtr> emoji,
|
not_null<EmojiPtr> emoji,
|
||||||
const QString &replacement)
|
const QString &replacement)
|
||||||
|
@ -230,18 +400,20 @@ void SuggestionsWidget::paintEvent(QPaintEvent *e) {
|
||||||
Ui::StickerHoverCorners);
|
Ui::StickerHoverCorners);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto now = crl::now();
|
||||||
|
const auto preview = st::windowBgOver->c;
|
||||||
for (auto i = from; i != till; ++i) {
|
for (auto i = from; i != till; ++i) {
|
||||||
const auto &row = _rows[i];
|
const auto &row = _rows[i];
|
||||||
const auto emoji = row.emoji;
|
const auto emoji = row.emoji;
|
||||||
const auto esize = Ui::Emoji::GetSizeLarge();
|
const auto esize = Ui::Emoji::GetSizeLarge();
|
||||||
const auto x = i * _oneWidth;
|
const auto size = esize / style::DevicePixelRatio();
|
||||||
const auto y = 0;
|
const auto x = i * _oneWidth + (_oneWidth - size) / 2;
|
||||||
Ui::Emoji::Draw(
|
const auto y = (_oneWidth - size) / 2;
|
||||||
p,
|
if (row.instance) {
|
||||||
emoji,
|
row.instance->object.paint(p, x, y, now, preview, false);
|
||||||
esize,
|
} else {
|
||||||
x + (_oneWidth - (esize / cIntRetinaFactor())) / 2,
|
Ui::Emoji::Draw(p, emoji, esize, x, y);
|
||||||
y + (_oneWidth - (esize / cIntRetinaFactor())) / 2);
|
}
|
||||||
}
|
}
|
||||||
paintFadings(p);
|
paintFadings(p);
|
||||||
}
|
}
|
||||||
|
@ -496,7 +668,10 @@ bool SuggestionsWidget::triggerSelectedRow() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SuggestionsWidget::triggerRow(const Row &row) const {
|
void SuggestionsWidget::triggerRow(const Row &row) const {
|
||||||
_triggered.fire(row.emoji->text());
|
_triggered.fire({
|
||||||
|
row.emoji->text(),
|
||||||
|
row.document ? Data::SerializeCustomEmojiId(row.document) : QString()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void SuggestionsWidget::enterEventHook(QEnterEvent *e) {
|
void SuggestionsWidget::enterEventHook(QEnterEvent *e) {
|
||||||
|
@ -525,7 +700,9 @@ SuggestionsController::SuggestionsController(
|
||||||
st::emojiSuggestionsDropdown);
|
st::emojiSuggestionsDropdown);
|
||||||
_container->setAutoHiding(false);
|
_container->setAutoHiding(false);
|
||||||
_suggestions = _container->setOwnedWidget(
|
_suggestions = _container->setOwnedWidget(
|
||||||
object_ptr<Ui::Emoji::SuggestionsWidget>(_container));
|
object_ptr<Ui::Emoji::SuggestionsWidget>(
|
||||||
|
_container,
|
||||||
|
session));
|
||||||
|
|
||||||
setReplaceCallback(nullptr);
|
setReplaceCallback(nullptr);
|
||||||
|
|
||||||
|
@ -559,8 +736,8 @@ SuggestionsController::SuggestionsController(
|
||||||
suggestionsUpdated(visible);
|
suggestionsUpdated(visible);
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
_suggestions->triggered(
|
_suggestions->triggered(
|
||||||
) | rpl::start_with_next([=](QString replacement) {
|
) | rpl::start_with_next([=](const SuggestionsWidget::Chosen &chosen) {
|
||||||
replaceCurrent(replacement);
|
replaceCurrent(chosen.emoji, chosen.customData);
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
Core::App().emojiKeywords().refreshed(
|
Core::App().emojiKeywords().refreshed(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
|
@ -589,8 +766,13 @@ SuggestionsController *SuggestionsController::Init(
|
||||||
result->setReplaceCallback([=](
|
result->setReplaceCallback([=](
|
||||||
int from,
|
int from,
|
||||||
int till,
|
int till,
|
||||||
const QString &replacement) {
|
const QString &replacement,
|
||||||
field->commitInstantReplacement(from, till, replacement);
|
const QString &customEmojiData) {
|
||||||
|
field->commitInstantReplacement(
|
||||||
|
from,
|
||||||
|
till,
|
||||||
|
replacement,
|
||||||
|
customEmojiData);
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -599,11 +781,16 @@ void SuggestionsController::setReplaceCallback(
|
||||||
Fn<void(
|
Fn<void(
|
||||||
int from,
|
int from,
|
||||||
int till,
|
int till,
|
||||||
const QString &replacement)> callback) {
|
const QString &replacement,
|
||||||
|
const QString &customEmojiData)> callback) {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
_replaceCallback = std::move(callback);
|
_replaceCallback = std::move(callback);
|
||||||
} else {
|
} else {
|
||||||
_replaceCallback = [=](int from, int till, const QString &replacement) {
|
_replaceCallback = [=](
|
||||||
|
int from,
|
||||||
|
int till,
|
||||||
|
const QString &replacement,
|
||||||
|
const QString &customEmojiData) {
|
||||||
auto cursor = _field->textCursor();
|
auto cursor = _field->textCursor();
|
||||||
cursor.setPosition(from);
|
cursor.setPosition(from);
|
||||||
cursor.setPosition(till, QTextCursor::KeepAnchor);
|
cursor.setPosition(till, QTextCursor::KeepAnchor);
|
||||||
|
@ -667,7 +854,9 @@ QString SuggestionsController::getEmojiQuery() {
|
||||||
if (from >= position || till < position) {
|
if (from >= position || till < position) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (fragment.charFormat().isImageFormat()) {
|
const auto format = fragment.charFormat();
|
||||||
|
if (format.isImageFormat()
|
||||||
|
|| format.objectType() == InputField::kCustomEmojiFormat) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_queryStartPosition = from;
|
_queryStartPosition = from;
|
||||||
|
@ -714,7 +903,9 @@ QString SuggestionsController::getEmojiQuery() {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SuggestionsController::replaceCurrent(const QString &replacement) {
|
void SuggestionsController::replaceCurrent(
|
||||||
|
const QString &replacement,
|
||||||
|
const QString &customEmojiData) {
|
||||||
const auto suggestion = getEmojiQuery();
|
const auto suggestion = getEmojiQuery();
|
||||||
if (suggestion.isEmpty()) {
|
if (suggestion.isEmpty()) {
|
||||||
showWithQuery(QString());
|
showWithQuery(QString());
|
||||||
|
@ -722,7 +913,7 @@ void SuggestionsController::replaceCurrent(const QString &replacement) {
|
||||||
const auto cursor = _field->textCursor();
|
const auto cursor = _field->textCursor();
|
||||||
const auto position = cursor.position();
|
const auto position = cursor.position();
|
||||||
const auto from = position - suggestion.size();
|
const auto from = position - suggestion.size();
|
||||||
_replaceCallback(from, position, replacement);
|
_replaceCallback(from, position, replacement, customEmojiData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,19 +27,28 @@ namespace Emoji {
|
||||||
|
|
||||||
class SuggestionsWidget final : public Ui::RpWidget {
|
class SuggestionsWidget final : public Ui::RpWidget {
|
||||||
public:
|
public:
|
||||||
SuggestionsWidget(QWidget *parent);
|
SuggestionsWidget(QWidget *parent, not_null<Main::Session*> session);
|
||||||
|
~SuggestionsWidget();
|
||||||
|
|
||||||
void showWithQuery(const QString &query, bool force = false);
|
void showWithQuery(const QString &query, bool force = false);
|
||||||
void selectFirstResult();
|
void selectFirstResult();
|
||||||
bool handleKeyEvent(int key);
|
bool handleKeyEvent(int key);
|
||||||
|
|
||||||
rpl::producer<bool> toggleAnimated() const;
|
[[nodiscard]] rpl::producer<bool> toggleAnimated() const;
|
||||||
rpl::producer<QString> triggered() const;
|
|
||||||
|
struct Chosen {
|
||||||
|
QString emoji;
|
||||||
|
QString customData;
|
||||||
|
};
|
||||||
|
[[nodiscard]] rpl::producer<Chosen> triggered() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct CustomInstance;
|
||||||
struct Row {
|
struct Row {
|
||||||
Row(not_null<EmojiPtr> emoji, const QString &replacement);
|
Row(not_null<EmojiPtr> emoji, const QString &replacement);
|
||||||
|
|
||||||
|
CustomInstance *instance = nullptr;
|
||||||
|
DocumentData *document = nullptr;
|
||||||
not_null<EmojiPtr> emoji;
|
not_null<EmojiPtr> emoji;
|
||||||
QString replacement;
|
QString replacement;
|
||||||
};
|
};
|
||||||
|
@ -56,7 +65,8 @@ private:
|
||||||
void scrollByWheelEvent(not_null<QWheelEvent*> e);
|
void scrollByWheelEvent(not_null<QWheelEvent*> e);
|
||||||
void paintFadings(Painter &p) const;
|
void paintFadings(Painter &p) const;
|
||||||
|
|
||||||
std::vector<Row> getRowsByQuery() const;
|
[[nodiscard]] std::vector<Row> getRowsByQuery() const;
|
||||||
|
[[nodiscard]] std::vector<Row> prependCustom(std::vector<Row> rows);
|
||||||
void resizeToRows();
|
void resizeToRows();
|
||||||
void setSelected(
|
void setSelected(
|
||||||
int selected,
|
int selected,
|
||||||
|
@ -77,8 +87,21 @@ private:
|
||||||
void scrollTo(int value, anim::type animated = anim::type::instant);
|
void scrollTo(int value, anim::type animated = anim::type::instant);
|
||||||
void stopAnimations();
|
void stopAnimations();
|
||||||
|
|
||||||
|
[[nodiscard]] not_null<CustomInstance*> resolveCustomInstance(
|
||||||
|
not_null<DocumentData*> document);
|
||||||
|
void scheduleRepaintTimer();
|
||||||
|
void invokeRepaints();
|
||||||
|
|
||||||
|
const not_null<Main::Session*> _session;
|
||||||
QString _query;
|
QString _query;
|
||||||
std::vector<Row> _rows;
|
std::vector<Row> _rows;
|
||||||
|
base::flat_map<
|
||||||
|
not_null<DocumentData*>,
|
||||||
|
std::unique_ptr<CustomInstance>> _instances;
|
||||||
|
base::flat_map<crl::time, crl::time> _repaints;
|
||||||
|
bool _repaintTimerScheduled = false;
|
||||||
|
base::Timer _repaintTimer;
|
||||||
|
crl::time _repaintNext = 0;
|
||||||
|
|
||||||
std::optional<QPoint> _lastMousePosition;
|
std::optional<QPoint> _lastMousePosition;
|
||||||
bool _mouseSelection = false;
|
bool _mouseSelection = false;
|
||||||
|
@ -96,7 +119,7 @@ private:
|
||||||
int _dragScrollStart = -1;
|
int _dragScrollStart = -1;
|
||||||
|
|
||||||
rpl::event_stream<bool> _toggleAnimated;
|
rpl::event_stream<bool> _toggleAnimated;
|
||||||
rpl::event_stream<QString> _triggered;
|
rpl::event_stream<Chosen> _triggered;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -119,7 +142,8 @@ public:
|
||||||
void setReplaceCallback(Fn<void(
|
void setReplaceCallback(Fn<void(
|
||||||
int from,
|
int from,
|
||||||
int till,
|
int till,
|
||||||
const QString &replacement)> callback);
|
const QString &replacement,
|
||||||
|
const QString &customEmojiData)> callback);
|
||||||
|
|
||||||
static SuggestionsController *Init(
|
static SuggestionsController *Init(
|
||||||
not_null<QWidget*> outer,
|
not_null<QWidget*> outer,
|
||||||
|
@ -135,7 +159,9 @@ private:
|
||||||
void suggestionsUpdated(bool visible);
|
void suggestionsUpdated(bool visible);
|
||||||
void updateGeometry();
|
void updateGeometry();
|
||||||
void updateForceHidden();
|
void updateForceHidden();
|
||||||
void replaceCurrent(const QString &replacement);
|
void replaceCurrent(
|
||||||
|
const QString &replacement,
|
||||||
|
const QString &customEmojiData);
|
||||||
bool fieldFilter(not_null<QEvent*> event);
|
bool fieldFilter(not_null<QEvent*> event);
|
||||||
bool outerFilter(not_null<QEvent*> event);
|
bool outerFilter(not_null<QEvent*> event);
|
||||||
|
|
||||||
|
@ -149,7 +175,8 @@ private:
|
||||||
Fn<void(
|
Fn<void(
|
||||||
int from,
|
int from,
|
||||||
int till,
|
int till,
|
||||||
const QString &replacement)> _replaceCallback;
|
const QString &replacement,
|
||||||
|
const QString &customEmojiData)> _replaceCallback;
|
||||||
base::unique_qptr<InnerDropdown> _container;
|
base::unique_qptr<InnerDropdown> _container;
|
||||||
QPointer<SuggestionsWidget> _suggestions;
|
QPointer<SuggestionsWidget> _suggestions;
|
||||||
base::unique_qptr<QObject> _fieldFilter;
|
base::unique_qptr<QObject> _fieldFilter;
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 1d34c64da8bc234c4d5dd8ebaff7f249d897c7d7
|
Subproject commit 0daf3d4ac70e587c80abe7685e7ad7512f6f39cf
|
Loading…
Add table
Reference in a new issue