mirror of
https://github.com/vale981/tdesktop
synced 2025-03-06 02:01:40 -05:00
Add large emoji implementation.
This commit is contained in:
parent
1d52ba7a42
commit
d298953653
24 changed files with 690 additions and 175 deletions
|
@ -9,57 +9,332 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "history/history_item.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/text/text_isolated_emoji.h"
|
||||
#include "ui/image/image_source.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "base/concurrent_timer.h"
|
||||
#include "apiwrap.h"
|
||||
#include "styles/style_history.h"
|
||||
|
||||
namespace Stickers {
|
||||
namespace details {
|
||||
|
||||
class EmojiImageLoader {
|
||||
public:
|
||||
EmojiImageLoader(
|
||||
crl::weak_on_queue<EmojiImageLoader> weak,
|
||||
int id);
|
||||
|
||||
[[nodiscard]] QImage prepare(const IsolatedEmoji &emoji);
|
||||
void switchTo(int id);
|
||||
|
||||
private:
|
||||
crl::weak_on_queue<EmojiImageLoader> _weak;
|
||||
std::optional<Ui::Emoji::UniversalImages> _images;
|
||||
|
||||
base::ConcurrentTimer _unloadTimer;
|
||||
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kRefreshTimeout = TimeId(7200);
|
||||
constexpr auto kUnloadTimeout = 5 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] QSize CalculateSize(const IsolatedEmoji &emoji) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto single = st::largeEmojiSize;
|
||||
const auto skip = st::largeEmojiSkip;
|
||||
const auto outline = st::largeEmojiOutline;
|
||||
const auto count = ranges::count_if(emoji.items, _1 != nullptr);
|
||||
const auto items = single * count + skip * (count - 1);
|
||||
return QSize(
|
||||
2 * outline + items,
|
||||
2 * outline + single
|
||||
) * cIntRetinaFactor();
|
||||
}
|
||||
|
||||
class ImageSource : public Images::Source {
|
||||
public:
|
||||
explicit ImageSource(
|
||||
const IsolatedEmoji &emoji,
|
||||
not_null<crl::object_on_queue<EmojiImageLoader>*> loader);
|
||||
|
||||
void load(Data::FileOrigin origin) override;
|
||||
void loadEvenCancelled(Data::FileOrigin origin) override;
|
||||
QImage takeLoaded() override;
|
||||
void unload() override;
|
||||
|
||||
void automaticLoad(
|
||||
Data::FileOrigin origin,
|
||||
const HistoryItem *item) override;
|
||||
void automaticLoadSettingsChanged() override;
|
||||
|
||||
bool loading() override;
|
||||
bool displayLoading() override;
|
||||
void cancel() override;
|
||||
float64 progress() override;
|
||||
int loadOffset() override;
|
||||
|
||||
const StorageImageLocation &location() override;
|
||||
void refreshFileReference(const QByteArray &data) override;
|
||||
std::optional<Storage::Cache::Key> cacheKey() override;
|
||||
void setDelayedStorageLocation(
|
||||
const StorageImageLocation &location) override;
|
||||
void performDelayedLoad(Data::FileOrigin origin) override;
|
||||
bool isDelayedStorageImage() const override;
|
||||
void setImageBytes(const QByteArray &bytes) override;
|
||||
|
||||
int width() override;
|
||||
int height() override;
|
||||
int bytesSize() override;
|
||||
void setInformation(int size, int width, int height) override;
|
||||
|
||||
QByteArray bytesForCache() override;
|
||||
|
||||
private:
|
||||
// While HistoryView::Element-s are almost never destroyed
|
||||
// we make loading of the image lazy.
|
||||
not_null<crl::object_on_queue<EmojiImageLoader>*> _loader;
|
||||
IsolatedEmoji _emoji;
|
||||
QImage _data;
|
||||
QByteArray _format;
|
||||
QByteArray _bytes;
|
||||
QSize _size;
|
||||
base::binary_guard _loading;
|
||||
|
||||
};
|
||||
|
||||
ImageSource::ImageSource(
|
||||
const IsolatedEmoji &emoji,
|
||||
not_null<crl::object_on_queue<EmojiImageLoader>*> loader)
|
||||
: _loader(loader)
|
||||
, _emoji(emoji)
|
||||
, _size(CalculateSize(emoji)) {
|
||||
}
|
||||
|
||||
void ImageSource::load(Data::FileOrigin origin) {
|
||||
if (!_data.isNull()) {
|
||||
return;
|
||||
}
|
||||
if (_bytes.isEmpty()) {
|
||||
_loader->with([
|
||||
this,
|
||||
emoji = _emoji,
|
||||
guard = _loading.make_guard()
|
||||
](EmojiImageLoader &loader) mutable {
|
||||
if (!guard) {
|
||||
return;
|
||||
}
|
||||
crl::on_main(std::move(guard), [this, image = loader.prepare(emoji)]{
|
||||
_data = image;
|
||||
Auth().downloaderTaskFinished().notify();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
_data = App::readImage(_bytes, &_format, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ImageSource::loadEvenCancelled(Data::FileOrigin origin) {
|
||||
load(origin);
|
||||
}
|
||||
|
||||
QImage ImageSource::takeLoaded() {
|
||||
load({});
|
||||
return _data;
|
||||
}
|
||||
|
||||
void ImageSource::unload() {
|
||||
if (_bytes.isEmpty() && !_data.isNull()) {
|
||||
if (_format != "JPG") {
|
||||
_format = "PNG";
|
||||
}
|
||||
{
|
||||
QBuffer buffer(&_bytes);
|
||||
_data.save(&buffer, _format);
|
||||
}
|
||||
Assert(!_bytes.isEmpty());
|
||||
}
|
||||
_data = QImage();
|
||||
}
|
||||
|
||||
void ImageSource::automaticLoad(
|
||||
Data::FileOrigin origin,
|
||||
const HistoryItem *item) {
|
||||
}
|
||||
|
||||
void ImageSource::automaticLoadSettingsChanged() {
|
||||
}
|
||||
|
||||
bool ImageSource::loading() {
|
||||
return _data.isNull() && _bytes.isEmpty();
|
||||
}
|
||||
|
||||
bool ImageSource::displayLoading() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ImageSource::cancel() {
|
||||
}
|
||||
|
||||
float64 ImageSource::progress() {
|
||||
return 1.;
|
||||
}
|
||||
|
||||
int ImageSource::loadOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const StorageImageLocation &ImageSource::location() {
|
||||
return StorageImageLocation::Invalid();
|
||||
}
|
||||
|
||||
void ImageSource::refreshFileReference(const QByteArray &data) {
|
||||
}
|
||||
|
||||
std::optional<Storage::Cache::Key> ImageSource::cacheKey() {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void ImageSource::setDelayedStorageLocation(
|
||||
const StorageImageLocation &location) {
|
||||
}
|
||||
|
||||
void ImageSource::performDelayedLoad(Data::FileOrigin origin) {
|
||||
}
|
||||
|
||||
bool ImageSource::isDelayedStorageImage() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ImageSource::setImageBytes(const QByteArray &bytes) {
|
||||
}
|
||||
|
||||
int ImageSource::width() {
|
||||
return _size.width();
|
||||
}
|
||||
|
||||
int ImageSource::height() {
|
||||
return _size.height();
|
||||
}
|
||||
|
||||
int ImageSource::bytesSize() {
|
||||
return _bytes.size();
|
||||
}
|
||||
|
||||
void ImageSource::setInformation(int size, int width, int height) {
|
||||
if (width && height) {
|
||||
_size = QSize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray ImageSource::bytesForCache() {
|
||||
auto result = QByteArray();
|
||||
{
|
||||
QBuffer buffer(&result);
|
||||
if (!_data.save(&buffer, _format)) {
|
||||
if (_data.save(&buffer, "PNG")) {
|
||||
_format = "PNG";
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EmojiPack::EmojiPack(not_null<Main::Session*> session) : _session(session) {
|
||||
EmojiImageLoader::EmojiImageLoader(
|
||||
crl::weak_on_queue<EmojiImageLoader> weak,
|
||||
int id)
|
||||
: _weak(std::move(weak))
|
||||
, _images(std::in_place, id)
|
||||
, _unloadTimer(_weak.runner(), [=] { _images->clear(); }) {
|
||||
}
|
||||
|
||||
QImage EmojiImageLoader::prepare(const IsolatedEmoji &emoji) {
|
||||
Expects(_images.has_value());
|
||||
|
||||
_images->ensureLoaded();
|
||||
auto result = QImage(
|
||||
CalculateSize(emoji),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
result.fill(Qt::transparent);
|
||||
{
|
||||
QPainter p(&result);
|
||||
auto x = st::largeEmojiOutline;
|
||||
const auto y = st::largeEmojiOutline;
|
||||
for (const auto &single : emoji.items) {
|
||||
if (!single) {
|
||||
break;
|
||||
}
|
||||
_images->draw(
|
||||
p,
|
||||
single,
|
||||
st::largeEmojiSize * cIntRetinaFactor(),
|
||||
x,
|
||||
y);
|
||||
x += st::largeEmojiSize + st::largeEmojiSkip;
|
||||
}
|
||||
}
|
||||
_unloadTimer.callOnce(kUnloadTimeout);
|
||||
return result;
|
||||
}
|
||||
|
||||
void EmojiImageLoader::switchTo(int id) {
|
||||
_images.emplace(id);
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
|
||||
EmojiPack::EmojiPack(not_null<Main::Session*> session)
|
||||
: _session(session)
|
||||
, _imageLoader(Ui::Emoji::CurrentSetId()) {
|
||||
refresh();
|
||||
|
||||
session->data().itemRemoved(
|
||||
) | rpl::filter([](not_null<const HistoryItem*> item) {
|
||||
return item->isSingleEmoji();
|
||||
return item->isIsolatedEmoji();
|
||||
}) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
|
||||
remove(item);
|
||||
}, _lifetime);
|
||||
|
||||
session->settings().largeEmojiChanges(
|
||||
) | rpl::start_with_next([=] {
|
||||
for (const auto &[emoji, document] : _map) {
|
||||
refreshItems(emoji);
|
||||
}
|
||||
refreshAll();
|
||||
}, _lifetime);
|
||||
|
||||
Ui::Emoji::Updated(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto id = Ui::Emoji::CurrentSetId();
|
||||
_images.clear();
|
||||
_imageLoader.with([=](details::EmojiImageLoader &loader) {
|
||||
loader.switchTo(id);
|
||||
});
|
||||
refreshAll();
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
bool EmojiPack::add(not_null<HistoryItem*> item, const QString &text) {
|
||||
EmojiPack::~EmojiPack() = default;
|
||||
|
||||
bool EmojiPack::add(not_null<HistoryItem*> item) {
|
||||
auto length = 0;
|
||||
const auto trimmed = text.trimmed();
|
||||
if (const auto emoji = Ui::Emoji::Find(trimmed, &length)) {
|
||||
if (length == trimmed.size()) {
|
||||
_items[emoji].emplace(item);
|
||||
return true;
|
||||
}
|
||||
if (const auto emoji = item->isolatedEmoji()) {
|
||||
_items[emoji].emplace(item);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EmojiPack::remove(not_null<const HistoryItem*> item) {
|
||||
if (!item->isSingleEmoji()) {
|
||||
return false;
|
||||
}
|
||||
void EmojiPack::remove(not_null<const HistoryItem*> item) {
|
||||
Expects(item->isIsolatedEmoji());
|
||||
|
||||
auto length = 0;
|
||||
const auto trimmed = item->originalString().trimmed();
|
||||
const auto emoji = Ui::Emoji::Find(trimmed, &length);
|
||||
Assert(emoji != nullptr);
|
||||
Assert(length == trimmed.size());
|
||||
const auto emoji = item->isolatedEmoji();
|
||||
const auto i = _items.find(emoji);
|
||||
Assert(i != end(_items));
|
||||
const auto j = i->second.find(item);
|
||||
|
@ -68,22 +343,29 @@ bool EmojiPack::remove(not_null<const HistoryItem*> item) {
|
|||
if (i->second.empty()) {
|
||||
_items.erase(i);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
DocumentData *EmojiPack::stickerForEmoji(not_null<HistoryItem*> item) {
|
||||
if (!item->isSingleEmoji() || !_session->settings().largeEmoji()) {
|
||||
DocumentData *EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) {
|
||||
Expects(!emoji.empty());
|
||||
|
||||
if (emoji.items[1] != nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
auto length = 0;
|
||||
const auto trimmed = item->originalString().trimmed();
|
||||
const auto emoji = Ui::Emoji::Find(trimmed, &length);
|
||||
Assert(emoji != nullptr);
|
||||
Assert(length == trimmed.size());
|
||||
const auto i = _map.find(emoji);
|
||||
const auto i = _map.find(emoji.items[0]);
|
||||
return (i != end(_map)) ? i->second.get() : nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Image> EmojiPack::image(const IsolatedEmoji &emoji) {
|
||||
const auto i = _images.emplace(emoji, std::weak_ptr<Image>()).first;
|
||||
if (const auto result = i->second.lock()) {
|
||||
return result;
|
||||
}
|
||||
auto result = std::make_shared<Image>(
|
||||
std::make_unique<details::ImageSource>(emoji, &_imageLoader));
|
||||
i->second = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
void EmojiPack::refresh() {
|
||||
if (_requestId) {
|
||||
return;
|
||||
|
@ -128,12 +410,23 @@ void EmojiPack::applySet(const MTPDmessages_stickerSet &data) {
|
|||
}
|
||||
}
|
||||
|
||||
void EmojiPack::refreshAll() {
|
||||
for (const auto &[emoji, list] : _items) {
|
||||
refreshItems(list);
|
||||
}
|
||||
}
|
||||
|
||||
void EmojiPack::refreshItems(EmojiPtr emoji) {
|
||||
const auto i = _items.find(emoji);
|
||||
const auto i = _items.find(IsolatedEmoji{ { emoji } });
|
||||
if (i == end(_items)) {
|
||||
return;
|
||||
}
|
||||
for (const auto &item : i->second) {
|
||||
refreshItems(i->second);
|
||||
}
|
||||
|
||||
void EmojiPack::refreshItems(
|
||||
const base::flat_set<not_null<HistoryItem*>> &list) {
|
||||
for (const auto &item : list) {
|
||||
_session->data().requestItemViewRefresh(item);
|
||||
}
|
||||
}
|
||||
|
@ -172,7 +465,7 @@ base::flat_map<uint64, not_null<DocumentData*>> EmojiPack::collectStickers(
|
|||
}
|
||||
|
||||
void EmojiPack::refreshDelayed() {
|
||||
App::CallDelayed(kRefreshTimeout, _session, [=] {
|
||||
App::CallDelayed(details::kRefreshTimeout, _session, [=] {
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/text/text_isolated_emoji.h"
|
||||
|
||||
#include <crl/crl_object_on_queue.h>
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
@ -14,18 +18,33 @@ class Session;
|
|||
class HistoryItem;
|
||||
class DocumentData;
|
||||
|
||||
namespace Ui {
|
||||
namespace Text {
|
||||
class String;
|
||||
} // namespace Text
|
||||
} // namespace Ui
|
||||
|
||||
namespace Stickers {
|
||||
namespace details {
|
||||
class EmojiImageLoader;
|
||||
} // namespace details
|
||||
|
||||
using IsolatedEmoji = Ui::Text::IsolatedEmoji;
|
||||
|
||||
class EmojiPack final {
|
||||
public:
|
||||
explicit EmojiPack(not_null<Main::Session*> session);
|
||||
~EmojiPack();
|
||||
|
||||
bool add(not_null<HistoryItem*> item, const QString &text);
|
||||
bool remove(not_null<const HistoryItem*> item);
|
||||
bool add(not_null<HistoryItem*> item);
|
||||
void remove(not_null<const HistoryItem*> item);
|
||||
|
||||
[[nodiscard]] DocumentData *stickerForEmoji(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] DocumentData *stickerForEmoji(const IsolatedEmoji &emoji);
|
||||
[[nodiscard]] std::shared_ptr<Image> image(const IsolatedEmoji &emoji);
|
||||
|
||||
private:
|
||||
class ImageLoader;
|
||||
|
||||
void refresh();
|
||||
void refreshDelayed();
|
||||
void applySet(const MTPDmessages_stickerSet &data);
|
||||
|
@ -34,14 +53,20 @@ private:
|
|||
const base::flat_map<uint64, not_null<DocumentData*>> &map);
|
||||
base::flat_map<uint64, not_null<DocumentData*>> collectStickers(
|
||||
const QVector<MTPDocument> &list) const;
|
||||
void refreshAll();
|
||||
void refreshItems(EmojiPtr emoji);
|
||||
void refreshItems(const base::flat_set<not_null<HistoryItem*>> &list);
|
||||
|
||||
not_null<Main::Session*> _session;
|
||||
base::flat_set<not_null<HistoryItem*>> _notLoaded;
|
||||
base::flat_map<EmojiPtr, not_null<DocumentData*>> _map;
|
||||
base::flat_map<EmojiPtr, base::flat_set<not_null<HistoryItem*>>> _items;
|
||||
base::flat_map<
|
||||
IsolatedEmoji,
|
||||
base::flat_set<not_null<HistoryItem*>>> _items;
|
||||
base::flat_map<IsolatedEmoji, std::weak_ptr<Image>> _images;
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
crl::object_on_queue<details::EmojiImageLoader> _imageLoader;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
|
|
@ -771,9 +771,7 @@ std::unique_ptr<HistoryView::Media> MediaFile::createView(
|
|||
if (_document->sticker()) {
|
||||
return std::make_unique<HistoryView::UnwrappedMedia>(
|
||||
message,
|
||||
std::make_unique<HistoryView::StickerContent>(
|
||||
message,
|
||||
_document));
|
||||
std::make_unique<HistoryView::Sticker>(message, _document));
|
||||
} else if (_document->isAnimation()) {
|
||||
return std::make_unique<HistoryView::Gif>(message, _document);
|
||||
} else if (_document->isVideoFile()) {
|
||||
|
|
|
@ -582,3 +582,8 @@ historyAudioOutDownload: icon {{ "history_audio_download", historyFileOutIconFg
|
|||
historyAudioOutDownloadSelected: icon {{ "history_audio_download", historyFileOutIconFgSelected }};
|
||||
|
||||
historySlowmodeCounterMargins: margins(0px, 0px, 10px, 0px);
|
||||
|
||||
largeEmojiSize: 36px;
|
||||
largeEmojiOutline: 1px;
|
||||
largeEmojiPadding: margins(0px, 0px, 0px, 20px);
|
||||
largeEmojiSkip: 4px;
|
||||
|
|
|
@ -1681,7 +1681,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
|||
const auto media = (view ? view->media() : nullptr);
|
||||
const auto mediaHasTextForCopy = media && media->hasTextForCopy();
|
||||
if (const auto document = media ? media->getDocument() : nullptr) {
|
||||
if (!item->isSingleEmoji() && document->sticker()) {
|
||||
if (!item->isIsolatedEmoji() && document->sticker()) {
|
||||
if (document->sticker()->set.type() != mtpc_inputStickerSetEmpty) {
|
||||
_menu->addAction(document->isStickerSetInstalled() ? tr::lng_context_pack_info(tr::now) : tr::lng_context_pack_add(tr::now), [=] {
|
||||
showStickerPackInfo(document);
|
||||
|
|
|
@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/text/text_isolated_emoji.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "storage/file_upload.h"
|
||||
#include "storage/storage_facade.h"
|
||||
|
@ -779,6 +780,10 @@ QString HistoryItem::inDialogsText(DrawInDialog way) const {
|
|||
return plainText;
|
||||
}
|
||||
|
||||
Ui::Text::IsolatedEmoji HistoryItem::isolatedEmoji() const {
|
||||
return Ui::Text::IsolatedEmoji();
|
||||
}
|
||||
|
||||
void HistoryItem::drawInDialog(
|
||||
Painter &p,
|
||||
const QRect &r,
|
||||
|
|
|
@ -163,8 +163,8 @@ public:
|
|||
[[nodiscard]] bool isGroupMigrate() const {
|
||||
return isGroupEssential() && isEmpty();
|
||||
}
|
||||
[[nodiscard]] bool isSingleEmoji() const {
|
||||
return _flags & MTPDmessage_ClientFlag::f_single_emoji;
|
||||
[[nodiscard]] bool isIsolatedEmoji() const {
|
||||
return _flags & MTPDmessage_ClientFlag::f_isolated_emoji;
|
||||
}
|
||||
[[nodiscard]] bool hasViews() const {
|
||||
return _flags & MTPDmessage::Flag::f_views;
|
||||
|
@ -226,9 +226,7 @@ public:
|
|||
virtual QString inReplyText() const {
|
||||
return inDialogsText(DrawInDialog::WithoutSender);
|
||||
}
|
||||
virtual QString originalString() const {
|
||||
return QString();
|
||||
}
|
||||
virtual Ui::Text::IsolatedEmoji isolatedEmoji() const;
|
||||
virtual TextWithEntities originalText() const {
|
||||
return TextWithEntities();
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/confirm_box.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/text/text_isolated_emoji.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "core/application.h"
|
||||
#include "layout.h"
|
||||
|
@ -1084,7 +1085,7 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
|
|||
}
|
||||
}
|
||||
|
||||
clearSingleEmoji();
|
||||
clearIsolatedEmoji();
|
||||
if (_media && _media->consumeMessageText(textWithEntities)) {
|
||||
setEmptyText();
|
||||
} else {
|
||||
|
@ -1100,7 +1101,7 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
|
|||
{ QString::fromUtf8(":-("), EntitiesInText() },
|
||||
Ui::ItemTextOptions(this));
|
||||
} else if (!_media) {
|
||||
checkSingleEmoji(textWithEntities.text);
|
||||
checkIsolatedEmoji();
|
||||
}
|
||||
_textWidth = -1;
|
||||
_textHeight = 0;
|
||||
|
@ -1117,17 +1118,17 @@ void HistoryMessage::setEmptyText() {
|
|||
_textHeight = 0;
|
||||
}
|
||||
|
||||
void HistoryMessage::clearSingleEmoji() {
|
||||
if (!(_flags & MTPDmessage_ClientFlag::f_single_emoji)) {
|
||||
void HistoryMessage::clearIsolatedEmoji() {
|
||||
if (!(_flags & MTPDmessage_ClientFlag::f_isolated_emoji)) {
|
||||
return;
|
||||
}
|
||||
history()->session().emojiStickersPack().remove(this);
|
||||
_flags &= ~MTPDmessage_ClientFlag::f_single_emoji;
|
||||
_flags &= ~MTPDmessage_ClientFlag::f_isolated_emoji;
|
||||
}
|
||||
|
||||
void HistoryMessage::checkSingleEmoji(const QString &text) {
|
||||
if (history()->session().emojiStickersPack().add(this, text)) {
|
||||
_flags |= MTPDmessage_ClientFlag::f_single_emoji;
|
||||
void HistoryMessage::checkIsolatedEmoji() {
|
||||
if (history()->session().emojiStickersPack().add(this)) {
|
||||
_flags |= MTPDmessage_ClientFlag::f_isolated_emoji;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1173,8 +1174,8 @@ void HistoryMessage::setReplyMarkup(const MTPReplyMarkup *markup) {
|
|||
}
|
||||
}
|
||||
|
||||
QString HistoryMessage::originalString() const {
|
||||
return emptyText() ? QString() : _text.toString();
|
||||
Ui::Text::IsolatedEmoji HistoryMessage::isolatedEmoji() const {
|
||||
return _text.toIsolatedEmoji();
|
||||
}
|
||||
|
||||
TextWithEntities HistoryMessage::originalText() const {
|
||||
|
|
|
@ -133,7 +133,7 @@ public:
|
|||
[[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const override;
|
||||
|
||||
void setText(const TextWithEntities &textWithEntities) override;
|
||||
[[nodiscard]] QString originalString() const override;
|
||||
[[nodiscard]] Ui::Text::IsolatedEmoji isolatedEmoji() const override;
|
||||
[[nodiscard]] TextWithEntities originalText() const override;
|
||||
[[nodiscard]] TextForMimeData clipboardText() const override;
|
||||
[[nodiscard]] bool textHasLinks() const override;
|
||||
|
@ -164,8 +164,8 @@ private:
|
|||
return _flags & MTPDmessage::Flag::f_legacy;
|
||||
}
|
||||
|
||||
void clearSingleEmoji();
|
||||
void checkSingleEmoji(const QString &text);
|
||||
void clearIsolatedEmoji();
|
||||
void checkIsolatedEmoji();
|
||||
|
||||
// For an invoice button we replace the button text with a "Receipt" key.
|
||||
// It should show the receipt for the payed invoice. Still let mobile apps do that.
|
||||
|
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/media/history_view_media.h"
|
||||
#include "history/view/media/history_view_media_grouped.h"
|
||||
#include "history/view/media/history_view_sticker.h"
|
||||
#include "history/view/media/history_view_large_emoji.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
#include "chat_helpers/stickers_emoji_pack.h"
|
||||
|
@ -341,13 +342,22 @@ void Element::refreshMedia() {
|
|||
return;
|
||||
}
|
||||
}
|
||||
const auto emojiStickers = &history()->session().emojiStickersPack();
|
||||
if (_data->media()) {
|
||||
_media = _data->media()->createView(this);
|
||||
} else if (const auto document = emojiStickers->stickerForEmoji(_data)) {
|
||||
_media = std::make_unique<UnwrappedMedia>(
|
||||
this,
|
||||
std::make_unique<StickerContent>(this, document));
|
||||
const auto session = &history()->session();
|
||||
if (const auto media = _data->media()) {
|
||||
_media = media->createView(this);
|
||||
} else if (_data->isIsolatedEmoji()
|
||||
&& session->settings().largeEmoji()) {
|
||||
const auto emoji = _data->isolatedEmoji();
|
||||
const auto emojiStickers = &session->emojiStickersPack();
|
||||
if (const auto document = emojiStickers->stickerForEmoji(emoji)) {
|
||||
_media = std::make_unique<UnwrappedMedia>(
|
||||
this,
|
||||
std::make_unique<Sticker>(this, document));
|
||||
} else {
|
||||
_media = std::make_unique<UnwrappedMedia>(
|
||||
this,
|
||||
std::make_unique<LargeEmoji>(this, emoji));
|
||||
}
|
||||
} else {
|
||||
_media = nullptr;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
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 "history/view/media/history_view_large_emoji.h"
|
||||
|
||||
#include "main/main_session.h"
|
||||
#include "chat_helpers/stickers_emoji_pack.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "layout.h"
|
||||
#include "styles/style_history.h"
|
||||
|
||||
namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
std::shared_ptr<Image> ResolveImage(
|
||||
not_null<Main::Session*> session,
|
||||
const Ui::Text::IsolatedEmoji &emoji) {
|
||||
return session->emojiStickersPack().image(emoji);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LargeEmoji::LargeEmoji(
|
||||
not_null<Element*> parent,
|
||||
Ui::Text::IsolatedEmoji emoji)
|
||||
: _parent(parent)
|
||||
, _emoji(emoji)
|
||||
, _image(ResolveImage(&parent->data()->history()->session(), emoji)) {
|
||||
}
|
||||
|
||||
QSize LargeEmoji::size() {
|
||||
const auto size = _image->size() / cIntRetinaFactor();
|
||||
const auto &padding = st::largeEmojiPadding;
|
||||
_size = QSize(
|
||||
padding.left() + size.width() + padding.right(),
|
||||
padding.top() + size.height() + padding.bottom());
|
||||
return _size;
|
||||
}
|
||||
|
||||
void LargeEmoji::draw(Painter &p, const QRect &r, bool selected) {
|
||||
_image->load(Data::FileOrigin());
|
||||
if (!_image->loaded()) {
|
||||
return;
|
||||
}
|
||||
const auto &padding = st::largeEmojiPadding;
|
||||
const auto o = Data::FileOrigin();
|
||||
const auto w = _size.width() - padding.left() - padding.right();
|
||||
const auto h = _size.height() - padding.top() - padding.bottom();
|
||||
const auto &c = st::msgStickerOverlay;
|
||||
const auto pixmap = selected
|
||||
? _image->pixColored(o, c, w, h)
|
||||
: _image->pix(o, w, h);
|
||||
p.drawPixmap(
|
||||
QPoint(
|
||||
r.x() + (r.width() - _size.width()) / 2,
|
||||
r.y() + (r.height() - _size.height()) / 2),
|
||||
pixmap);
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
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 "history/view/media/history_view_media_unwrapped.h"
|
||||
#include "ui/text/text_isolated_emoji.h"
|
||||
|
||||
namespace Data {
|
||||
struct FileOrigin;
|
||||
} // namespace Data
|
||||
|
||||
namespace Lottie {
|
||||
class SinglePlayer;
|
||||
} // namespace Lottie
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
class LargeEmoji final : public UnwrappedMedia::Content {
|
||||
public:
|
||||
LargeEmoji(
|
||||
not_null<Element*> parent,
|
||||
Ui::Text::IsolatedEmoji emoji);
|
||||
|
||||
QSize size() override;
|
||||
void draw(Painter &p, const QRect &r, bool selected) override;
|
||||
|
||||
private:
|
||||
const not_null<Element*> _parent;
|
||||
const Ui::Text::IsolatedEmoji _emoji;
|
||||
std::shared_ptr<Image> _image;
|
||||
QSize _size;
|
||||
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
|
@ -72,7 +72,7 @@ std::unique_ptr<Media> CreateAttach(
|
|||
if (document->sticker()) {
|
||||
return std::make_unique<UnwrappedMedia>(
|
||||
parent,
|
||||
std::make_unique<StickerContent>(parent, document));
|
||||
std::make_unique<Sticker>(parent, document));
|
||||
} else if (document->isAnimation()) {
|
||||
return std::make_unique<Gif>(parent, document);
|
||||
} else if (document->isVideoFile()) {
|
||||
|
|
|
@ -31,8 +31,9 @@ QSize UnwrappedMedia::countOptimalSize() {
|
|||
_contentSize = NonEmptySize(DownscaledSize(
|
||||
_content->size(),
|
||||
{ st::maxStickerSize, st::maxStickerSize }));
|
||||
const auto minimal = st::largeEmojiSize;
|
||||
auto maxWidth = std::max(_contentSize.width(), st::minPhotoSize);
|
||||
auto minHeight = std::max(_contentSize.height(), st::minPhotoSize);
|
||||
auto minHeight = std::max(_contentSize.height(), minimal);
|
||||
accumulate_max(
|
||||
maxWidth,
|
||||
_parent->infoWidth() + 2 * st::msgDateImgPadding.x());
|
||||
|
|
|
@ -22,8 +22,12 @@ public:
|
|||
class Content {
|
||||
public:
|
||||
[[nodiscard]] virtual QSize size() = 0;
|
||||
|
||||
virtual void draw(Painter &p, const QRect &r, bool selected) = 0;
|
||||
[[nodiscard]] virtual ClickHandlerPtr link() = 0;
|
||||
|
||||
[[nodiscard]] virtual ClickHandlerPtr link() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual DocumentData *document() {
|
||||
return nullptr;
|
||||
|
|
|
@ -34,7 +34,7 @@ double GetEmojiStickerZoom(not_null<Main::Session*> session) {
|
|||
|
||||
} // namespace
|
||||
|
||||
StickerContent::StickerContent(
|
||||
Sticker::Sticker(
|
||||
not_null<Element*> parent,
|
||||
not_null<DocumentData*> document)
|
||||
: _parent(parent)
|
||||
|
@ -42,15 +42,15 @@ StickerContent::StickerContent(
|
|||
_document->loadThumbnail(parent->data()->fullId());
|
||||
}
|
||||
|
||||
StickerContent::~StickerContent() {
|
||||
Sticker::~Sticker() {
|
||||
unloadLottie();
|
||||
}
|
||||
|
||||
bool StickerContent::isEmojiSticker() const {
|
||||
bool Sticker::isEmojiSticker() const {
|
||||
return (_parent->data()->media() == nullptr);
|
||||
}
|
||||
|
||||
QSize StickerContent::size() {
|
||||
QSize Sticker::size() {
|
||||
_size = _document->dimensions;
|
||||
if (isEmojiSticker()) {
|
||||
constexpr auto kIdealStickerSize = 512;
|
||||
|
@ -63,10 +63,7 @@ QSize StickerContent::size() {
|
|||
return _size;
|
||||
}
|
||||
|
||||
void StickerContent::draw(
|
||||
Painter &p,
|
||||
const QRect &r,
|
||||
bool selected) {
|
||||
void Sticker::draw(Painter &p, const QRect &r, bool selected) {
|
||||
const auto sticker = _document->sticker();
|
||||
if (!sticker) {
|
||||
return;
|
||||
|
@ -85,7 +82,7 @@ void StickerContent::draw(
|
|||
}
|
||||
}
|
||||
|
||||
void StickerContent::paintLottie(Painter &p, const QRect &r, bool selected) {
|
||||
void Sticker::paintLottie(Painter &p, const QRect &r, bool selected) {
|
||||
auto request = Lottie::FrameRequest();
|
||||
request.box = _size * cIntRetinaFactor();
|
||||
if (selected) {
|
||||
|
@ -114,7 +111,7 @@ void StickerContent::paintLottie(Painter &p, const QRect &r, bool selected) {
|
|||
}
|
||||
}
|
||||
|
||||
void StickerContent::paintPixmap(Painter &p, const QRect &r, bool selected) {
|
||||
void Sticker::paintPixmap(Painter &p, const QRect &r, bool selected) {
|
||||
const auto pixmap = paintedPixmap(selected);
|
||||
if (!pixmap.isNull()) {
|
||||
p.drawPixmap(
|
||||
|
@ -125,7 +122,7 @@ void StickerContent::paintPixmap(Painter &p, const QRect &r, bool selected) {
|
|||
}
|
||||
}
|
||||
|
||||
QPixmap StickerContent::paintedPixmap(bool selected) const {
|
||||
QPixmap Sticker::paintedPixmap(bool selected) const {
|
||||
const auto o = _parent->data()->fullId();
|
||||
const auto w = _size.width();
|
||||
const auto h = _size.height();
|
||||
|
@ -157,7 +154,7 @@ QPixmap StickerContent::paintedPixmap(bool selected) const {
|
|||
return QPixmap();
|
||||
}
|
||||
|
||||
void StickerContent::refreshLink() {
|
||||
void Sticker::refreshLink() {
|
||||
if (_link) {
|
||||
return;
|
||||
}
|
||||
|
@ -180,7 +177,7 @@ void StickerContent::refreshLink() {
|
|||
}
|
||||
}
|
||||
|
||||
void StickerContent::setupLottie() {
|
||||
void Sticker::setupLottie() {
|
||||
_lottie = Stickers::LottiePlayerFromDocument(
|
||||
_document,
|
||||
Stickers::LottieSize::MessageHistory,
|
||||
|
@ -198,7 +195,7 @@ void StickerContent::setupLottie() {
|
|||
}, _lifetime);
|
||||
}
|
||||
|
||||
void StickerContent::unloadLottie() {
|
||||
void Sticker::unloadLottie() {
|
||||
if (!_lottie) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "history/view/media/history_view_media_unwrapped.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
namespace Data {
|
||||
struct FileOrigin;
|
||||
|
@ -21,14 +20,14 @@ class SinglePlayer;
|
|||
|
||||
namespace HistoryView {
|
||||
|
||||
class StickerContent final
|
||||
class Sticker final
|
||||
: public UnwrappedMedia::Content
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
StickerContent(
|
||||
Sticker(
|
||||
not_null<Element*> parent,
|
||||
not_null<DocumentData*> document);
|
||||
~StickerContent();
|
||||
~Sticker();
|
||||
|
||||
QSize size() override;
|
||||
void draw(Painter &p, const QRect &r, bool selected) override;
|
||||
|
|
|
@ -63,8 +63,8 @@ enum class MTPDmessage_ClientFlag : uint32 {
|
|||
// message was an outgoing message and failed to be sent
|
||||
f_failed = (1U << 22),
|
||||
|
||||
// message has no media and only a single emoji text
|
||||
f_single_emoji = (1U << 21),
|
||||
// message has no media and only a several emoji text
|
||||
f_isolated_emoji = (1U << 21),
|
||||
|
||||
// update this when adding new client side flags
|
||||
MIN_FIELD = (1U << 21),
|
||||
|
|
|
@ -61,26 +61,6 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class UniversalImages {
|
||||
public:
|
||||
explicit UniversalImages(int id);
|
||||
|
||||
int id() const;
|
||||
bool ensureLoaded();
|
||||
void clear();
|
||||
|
||||
void draw(QPainter &p, EmojiPtr emoji, int size, int x, int y) const;
|
||||
|
||||
// This method must be thread safe and so it is called after
|
||||
// the _id value is fixed and all _sprites are loaded.
|
||||
QImage generate(int size, int index) const;
|
||||
|
||||
private:
|
||||
int _id = 0;
|
||||
std::vector<QImage> _sprites;
|
||||
|
||||
};
|
||||
|
||||
auto SizeNormal = -1;
|
||||
auto SizeLarge = -1;
|
||||
auto SpritesCount = -1;
|
||||
|
@ -359,6 +339,71 @@ std::vector<QImage> LoadAndValidateSprites(int id) {
|
|||
return result;
|
||||
}
|
||||
|
||||
void AppendPartToResult(TextWithEntities &result, const QChar *start, const QChar *from, const QChar *to) {
|
||||
if (to <= from) {
|
||||
return;
|
||||
}
|
||||
for (auto &entity : result.entities) {
|
||||
if (entity.offset() >= to - start) break;
|
||||
if (entity.offset() + entity.length() < from - start) continue;
|
||||
if (entity.offset() >= from - start) {
|
||||
entity.extendToLeft(from - start - result.text.size());
|
||||
}
|
||||
if (entity.offset() + entity.length() <= to - start) {
|
||||
entity.shrinkFromRight(from - start - result.text.size());
|
||||
}
|
||||
}
|
||||
result.text.append(from, to - from);
|
||||
}
|
||||
|
||||
bool IsReplacementPart(ushort ch) {
|
||||
return (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch == '-') || (ch == '+') || (ch == '_');
|
||||
}
|
||||
|
||||
EmojiPtr FindReplacement(const QChar *start, const QChar *end, int *outLength) {
|
||||
if (start != end && *start == ':') {
|
||||
auto maxLength = GetSuggestionMaxLength();
|
||||
for (auto till = start + 1; till != end; ++till) {
|
||||
if (*till == ':') {
|
||||
auto text = QString::fromRawData(start, till + 1 - start);
|
||||
auto emoji = GetSuggestionEmoji(QStringToUTF16(text));
|
||||
auto result = Find(QStringFromUTF16(emoji));
|
||||
if (result) {
|
||||
if (outLength) *outLength = (till + 1 - start);
|
||||
}
|
||||
return result;
|
||||
} else if (!IsReplacementPart(till->unicode()) || (till - start) > maxLength) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return internal::FindReplace(start, end, outLength);
|
||||
}
|
||||
|
||||
void ClearUniversalChecked() {
|
||||
Expects(InstanceNormal != nullptr && InstanceLarge != nullptr);
|
||||
|
||||
if (InstanceNormal->cached() && InstanceLarge->cached() && Universal) {
|
||||
Universal->clear();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace internal {
|
||||
|
||||
QString CacheFileFolder() {
|
||||
return cWorkingDir() + "tdata/emoji";
|
||||
}
|
||||
|
||||
QString SetDataPath(int id) {
|
||||
Expects(IsValidSetId(id) && id != 0);
|
||||
|
||||
return CacheFileFolder() + "/set" + QString::number(id);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
UniversalImages::UniversalImages(int id) : _id(id) {
|
||||
Expects(IsValidSetId(id));
|
||||
}
|
||||
|
@ -452,71 +497,6 @@ QImage UniversalImages::generate(int size, int index) const {
|
|||
return result;
|
||||
}
|
||||
|
||||
void AppendPartToResult(TextWithEntities &result, const QChar *start, const QChar *from, const QChar *to) {
|
||||
if (to <= from) {
|
||||
return;
|
||||
}
|
||||
for (auto &entity : result.entities) {
|
||||
if (entity.offset() >= to - start) break;
|
||||
if (entity.offset() + entity.length() < from - start) continue;
|
||||
if (entity.offset() >= from - start) {
|
||||
entity.extendToLeft(from - start - result.text.size());
|
||||
}
|
||||
if (entity.offset() + entity.length() <= to - start) {
|
||||
entity.shrinkFromRight(from - start - result.text.size());
|
||||
}
|
||||
}
|
||||
result.text.append(from, to - from);
|
||||
}
|
||||
|
||||
bool IsReplacementPart(ushort ch) {
|
||||
return (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch == '-') || (ch == '+') || (ch == '_');
|
||||
}
|
||||
|
||||
EmojiPtr FindReplacement(const QChar *start, const QChar *end, int *outLength) {
|
||||
if (start != end && *start == ':') {
|
||||
auto maxLength = GetSuggestionMaxLength();
|
||||
for (auto till = start + 1; till != end; ++till) {
|
||||
if (*till == ':') {
|
||||
auto text = QString::fromRawData(start, till + 1 - start);
|
||||
auto emoji = GetSuggestionEmoji(QStringToUTF16(text));
|
||||
auto result = Find(QStringFromUTF16(emoji));
|
||||
if (result) {
|
||||
if (outLength) *outLength = (till + 1 - start);
|
||||
}
|
||||
return result;
|
||||
} else if (!IsReplacementPart(till->unicode()) || (till - start) > maxLength) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return internal::FindReplace(start, end, outLength);
|
||||
}
|
||||
|
||||
void ClearUniversalChecked() {
|
||||
Expects(InstanceNormal != nullptr && InstanceLarge != nullptr);
|
||||
|
||||
if (InstanceNormal->cached() && InstanceLarge->cached() && Universal) {
|
||||
Universal->clear();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace internal {
|
||||
|
||||
QString CacheFileFolder() {
|
||||
return cWorkingDir() + "tdata/emoji";
|
||||
}
|
||||
|
||||
QString SetDataPath(int id) {
|
||||
Expects(IsValidSetId(id) && id != 0);
|
||||
|
||||
return CacheFileFolder() + "/set" + QString::number(id);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
void Init() {
|
||||
internal::Init();
|
||||
|
||||
|
|
|
@ -167,5 +167,25 @@ rpl::producer<> UpdatedRecent();
|
|||
const QPixmap &SinglePixmap(EmojiPtr emoji, int fontHeight);
|
||||
void Draw(QPainter &p, EmojiPtr emoji, int size, int x, int y);
|
||||
|
||||
class UniversalImages {
|
||||
public:
|
||||
explicit UniversalImages(int id);
|
||||
|
||||
int id() const;
|
||||
bool ensureLoaded();
|
||||
void clear();
|
||||
|
||||
void draw(QPainter &p, EmojiPtr emoji, int size, int x, int y) const;
|
||||
|
||||
// This method must be thread safe and so it is called after
|
||||
// the _id value is fixed and all _sprites are loaded.
|
||||
QImage generate(int size, int index) const;
|
||||
|
||||
private:
|
||||
int _id = 0;
|
||||
std::vector<QImage> _sprites;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Emoji
|
||||
} // namespace Ui
|
||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "core/click_handler_types.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "ui/text/text_block.h"
|
||||
#include "ui/text/text_isolated_emoji.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "platform/platform_info.h"
|
||||
|
@ -3311,6 +3312,25 @@ TextForMimeData String::toText(
|
|||
return result;
|
||||
}
|
||||
|
||||
IsolatedEmoji String::toIsolatedEmoji() const {
|
||||
auto result = IsolatedEmoji();
|
||||
const auto skip = (_blocks.empty()
|
||||
|| _blocks.back()->type() != TextBlockTSkip) ? 0 : 1;
|
||||
if (_blocks.size() > kIsolatedEmojiLimit + skip) {
|
||||
return IsolatedEmoji();
|
||||
}
|
||||
auto index = 0;
|
||||
for (const auto &block : _blocks) {
|
||||
const auto type = block->type();
|
||||
if (type == TextBlockTEmoji) {
|
||||
result.items[index++] = static_cast<EmojiBlock*>(block.get())->emoji;
|
||||
} else if (type != TextBlockTSkip) {
|
||||
return IsolatedEmoji();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void String::clear() {
|
||||
clearFields();
|
||||
_text.clear();
|
||||
|
|
|
@ -7,8 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/click_handler.h"
|
||||
#include "ui/text/text_entity.h"
|
||||
#include "core/click_handler.h"
|
||||
#include "base/flags.h"
|
||||
|
||||
#include <private/qfixed_p.h>
|
||||
|
@ -70,6 +70,7 @@ namespace Ui {
|
|||
namespace Text {
|
||||
|
||||
class AbstractBlock;
|
||||
struct IsolatedEmoji;
|
||||
|
||||
struct StateRequest {
|
||||
enum class Flag {
|
||||
|
@ -176,6 +177,7 @@ public:
|
|||
TextSelection selection = AllTextSelection) const;
|
||||
TextForMimeData toTextForMimeData(
|
||||
TextSelection selection = AllTextSelection) const;
|
||||
IsolatedEmoji toIsolatedEmoji() const;
|
||||
|
||||
bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation
|
||||
if (_text.size() < maxdots) return false;
|
||||
|
|
46
Telegram/SourceFiles/ui/text/text_isolated_emoji.h
Normal file
46
Telegram/SourceFiles/ui/text/text_isolated_emoji.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
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
|
||||
|
||||
namespace Ui {
|
||||
namespace Text {
|
||||
|
||||
inline constexpr auto kIsolatedEmojiLimit = 3;
|
||||
|
||||
struct IsolatedEmoji {
|
||||
using Items = std::array<EmojiPtr, kIsolatedEmojiLimit>;
|
||||
Items items = { { nullptr } };
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return items[0] == nullptr;
|
||||
}
|
||||
[[nodiscard]] explicit operator bool() const {
|
||||
return !empty();
|
||||
}
|
||||
[[nodiscard]] bool operator<(const IsolatedEmoji &other) const {
|
||||
return items < other.items;
|
||||
}
|
||||
[[nodiscard]] bool operator==(const IsolatedEmoji &other) const {
|
||||
return items == other.items;
|
||||
}
|
||||
[[nodiscard]] bool operator>(const IsolatedEmoji &other) const {
|
||||
return other < *this;
|
||||
}
|
||||
[[nodiscard]] bool operator<=(const IsolatedEmoji &other) const {
|
||||
return !(other < *this);
|
||||
}
|
||||
[[nodiscard]] bool operator>=(const IsolatedEmoji &other) const {
|
||||
return !(*this < other);
|
||||
}
|
||||
[[nodiscard]] bool operator!=(const IsolatedEmoji &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Text
|
||||
} // namespace Ui
|
|
@ -288,6 +288,8 @@
|
|||
<(src_loc)/history/view/media/history_view_gif.cpp
|
||||
<(src_loc)/history/view/media/history_view_invoice.h
|
||||
<(src_loc)/history/view/media/history_view_invoice.cpp
|
||||
<(src_loc)/history/view/media/history_view_large_emoji.h
|
||||
<(src_loc)/history/view/media/history_view_large_emoji.cpp
|
||||
<(src_loc)/history/view/media/history_view_location.h
|
||||
<(src_loc)/history/view/media/history_view_location.cpp
|
||||
<(src_loc)/history/view/media/history_view_media.h
|
||||
|
@ -760,6 +762,7 @@
|
|||
<(src_loc)/ui/text/text_block.h
|
||||
<(src_loc)/ui/text/text_entity.cpp
|
||||
<(src_loc)/ui/text/text_entity.h
|
||||
<(src_loc)/ui/text/text_isolated_emoji.h
|
||||
<(src_loc)/ui/text/text_utilities.cpp
|
||||
<(src_loc)/ui/text/text_utilities.h
|
||||
<(src_loc)/ui/toast/toast.cpp
|
||||
|
|
Loading…
Add table
Reference in a new issue