mirror of https://github.com/procxx/kepka.git
Add large emoji implementation.
This commit is contained in:
parent
1d52ba7a42
commit
d298953653
|
@ -9,57 +9,332 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "ui/emoji_config.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 "main/main_session.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
|
#include "base/concurrent_timer.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "styles/style_history.h"
|
||||||
|
|
||||||
namespace Stickers {
|
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 {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kRefreshTimeout = TimeId(7200);
|
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
|
} // 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();
|
refresh();
|
||||||
|
|
||||||
session->data().itemRemoved(
|
session->data().itemRemoved(
|
||||||
) | rpl::filter([](not_null<const HistoryItem*> item) {
|
) | rpl::filter([](not_null<const HistoryItem*> item) {
|
||||||
return item->isSingleEmoji();
|
return item->isIsolatedEmoji();
|
||||||
}) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
|
}) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
|
||||||
remove(item);
|
remove(item);
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
|
|
||||||
session->settings().largeEmojiChanges(
|
session->settings().largeEmojiChanges(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
for (const auto &[emoji, document] : _map) {
|
refreshAll();
|
||||||
refreshItems(emoji);
|
}, _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);
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EmojiPack::add(not_null<HistoryItem*> item, const QString &text) {
|
EmojiPack::~EmojiPack() = default;
|
||||||
|
|
||||||
|
bool EmojiPack::add(not_null<HistoryItem*> item) {
|
||||||
auto length = 0;
|
auto length = 0;
|
||||||
const auto trimmed = text.trimmed();
|
if (const auto emoji = item->isolatedEmoji()) {
|
||||||
if (const auto emoji = Ui::Emoji::Find(trimmed, &length)) {
|
_items[emoji].emplace(item);
|
||||||
if (length == trimmed.size()) {
|
return true;
|
||||||
_items[emoji].emplace(item);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EmojiPack::remove(not_null<const HistoryItem*> item) {
|
void EmojiPack::remove(not_null<const HistoryItem*> item) {
|
||||||
if (!item->isSingleEmoji()) {
|
Expects(item->isIsolatedEmoji());
|
||||||
return false;
|
|
||||||
}
|
|
||||||
auto length = 0;
|
auto length = 0;
|
||||||
const auto trimmed = item->originalString().trimmed();
|
const auto emoji = item->isolatedEmoji();
|
||||||
const auto emoji = Ui::Emoji::Find(trimmed, &length);
|
|
||||||
Assert(emoji != nullptr);
|
|
||||||
Assert(length == trimmed.size());
|
|
||||||
const auto i = _items.find(emoji);
|
const auto i = _items.find(emoji);
|
||||||
Assert(i != end(_items));
|
Assert(i != end(_items));
|
||||||
const auto j = i->second.find(item);
|
const auto j = i->second.find(item);
|
||||||
|
@ -68,22 +343,29 @@ bool EmojiPack::remove(not_null<const HistoryItem*> item) {
|
||||||
if (i->second.empty()) {
|
if (i->second.empty()) {
|
||||||
_items.erase(i);
|
_items.erase(i);
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DocumentData *EmojiPack::stickerForEmoji(not_null<HistoryItem*> item) {
|
DocumentData *EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) {
|
||||||
if (!item->isSingleEmoji() || !_session->settings().largeEmoji()) {
|
Expects(!emoji.empty());
|
||||||
|
|
||||||
|
if (emoji.items[1] != nullptr) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
auto length = 0;
|
const auto i = _map.find(emoji.items[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);
|
|
||||||
return (i != end(_map)) ? i->second.get() : nullptr;
|
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() {
|
void EmojiPack::refresh() {
|
||||||
if (_requestId) {
|
if (_requestId) {
|
||||||
return;
|
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) {
|
void EmojiPack::refreshItems(EmojiPtr emoji) {
|
||||||
const auto i = _items.find(emoji);
|
const auto i = _items.find(IsolatedEmoji{ { emoji } });
|
||||||
if (i == end(_items)) {
|
if (i == end(_items)) {
|
||||||
return;
|
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);
|
_session->data().requestItemViewRefresh(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,7 +465,7 @@ base::flat_map<uint64, not_null<DocumentData*>> EmojiPack::collectStickers(
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmojiPack::refreshDelayed() {
|
void EmojiPack::refreshDelayed() {
|
||||||
App::CallDelayed(kRefreshTimeout, _session, [=] {
|
App::CallDelayed(details::kRefreshTimeout, _session, [=] {
|
||||||
refresh();
|
refresh();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/text/text_isolated_emoji.h"
|
||||||
|
|
||||||
|
#include <crl/crl_object_on_queue.h>
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
class Session;
|
class Session;
|
||||||
} // namespace Main
|
} // namespace Main
|
||||||
|
@ -14,18 +18,33 @@ class Session;
|
||||||
class HistoryItem;
|
class HistoryItem;
|
||||||
class DocumentData;
|
class DocumentData;
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace Text {
|
||||||
|
class String;
|
||||||
|
} // namespace Text
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Stickers {
|
namespace Stickers {
|
||||||
|
namespace details {
|
||||||
|
class EmojiImageLoader;
|
||||||
|
} // namespace details
|
||||||
|
|
||||||
|
using IsolatedEmoji = Ui::Text::IsolatedEmoji;
|
||||||
|
|
||||||
class EmojiPack final {
|
class EmojiPack final {
|
||||||
public:
|
public:
|
||||||
explicit EmojiPack(not_null<Main::Session*> session);
|
explicit EmojiPack(not_null<Main::Session*> session);
|
||||||
|
~EmojiPack();
|
||||||
|
|
||||||
bool add(not_null<HistoryItem*> item, const QString &text);
|
bool add(not_null<HistoryItem*> item);
|
||||||
bool remove(not_null<const 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:
|
private:
|
||||||
|
class ImageLoader;
|
||||||
|
|
||||||
void refresh();
|
void refresh();
|
||||||
void refreshDelayed();
|
void refreshDelayed();
|
||||||
void applySet(const MTPDmessages_stickerSet &data);
|
void applySet(const MTPDmessages_stickerSet &data);
|
||||||
|
@ -34,14 +53,20 @@ private:
|
||||||
const base::flat_map<uint64, not_null<DocumentData*>> &map);
|
const base::flat_map<uint64, not_null<DocumentData*>> &map);
|
||||||
base::flat_map<uint64, not_null<DocumentData*>> collectStickers(
|
base::flat_map<uint64, not_null<DocumentData*>> collectStickers(
|
||||||
const QVector<MTPDocument> &list) const;
|
const QVector<MTPDocument> &list) const;
|
||||||
|
void refreshAll();
|
||||||
void refreshItems(EmojiPtr emoji);
|
void refreshItems(EmojiPtr emoji);
|
||||||
|
void refreshItems(const base::flat_set<not_null<HistoryItem*>> &list);
|
||||||
|
|
||||||
not_null<Main::Session*> _session;
|
not_null<Main::Session*> _session;
|
||||||
base::flat_set<not_null<HistoryItem*>> _notLoaded;
|
|
||||||
base::flat_map<EmojiPtr, not_null<DocumentData*>> _map;
|
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;
|
mtpRequestId _requestId = 0;
|
||||||
|
|
||||||
|
crl::object_on_queue<details::EmojiImageLoader> _imageLoader;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -771,9 +771,7 @@ std::unique_ptr<HistoryView::Media> MediaFile::createView(
|
||||||
if (_document->sticker()) {
|
if (_document->sticker()) {
|
||||||
return std::make_unique<HistoryView::UnwrappedMedia>(
|
return std::make_unique<HistoryView::UnwrappedMedia>(
|
||||||
message,
|
message,
|
||||||
std::make_unique<HistoryView::StickerContent>(
|
std::make_unique<HistoryView::Sticker>(message, _document));
|
||||||
message,
|
|
||||||
_document));
|
|
||||||
} else if (_document->isAnimation()) {
|
} else if (_document->isAnimation()) {
|
||||||
return std::make_unique<HistoryView::Gif>(message, _document);
|
return std::make_unique<HistoryView::Gif>(message, _document);
|
||||||
} else if (_document->isVideoFile()) {
|
} else if (_document->isVideoFile()) {
|
||||||
|
|
|
@ -582,3 +582,8 @@ historyAudioOutDownload: icon {{ "history_audio_download", historyFileOutIconFg
|
||||||
historyAudioOutDownloadSelected: icon {{ "history_audio_download", historyFileOutIconFgSelected }};
|
historyAudioOutDownloadSelected: icon {{ "history_audio_download", historyFileOutIconFgSelected }};
|
||||||
|
|
||||||
historySlowmodeCounterMargins: margins(0px, 0px, 10px, 0px);
|
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 media = (view ? view->media() : nullptr);
|
||||||
const auto mediaHasTextForCopy = media && media->hasTextForCopy();
|
const auto mediaHasTextForCopy = media && media->hasTextForCopy();
|
||||||
if (const auto document = media ? media->getDocument() : nullptr) {
|
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) {
|
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), [=] {
|
_menu->addAction(document->isStickerSetInstalled() ? tr::lng_context_pack_info(tr::now) : tr::lng_context_pack_add(tr::now), [=] {
|
||||||
showStickerPackInfo(document);
|
showStickerPackInfo(document);
|
||||||
|
|
|
@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "media/clip/media_clip_reader.h"
|
#include "media/clip/media_clip_reader.h"
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
|
#include "ui/text/text_isolated_emoji.h"
|
||||||
#include "ui/text_options.h"
|
#include "ui/text_options.h"
|
||||||
#include "storage/file_upload.h"
|
#include "storage/file_upload.h"
|
||||||
#include "storage/storage_facade.h"
|
#include "storage/storage_facade.h"
|
||||||
|
@ -779,6 +780,10 @@ QString HistoryItem::inDialogsText(DrawInDialog way) const {
|
||||||
return plainText;
|
return plainText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ui::Text::IsolatedEmoji HistoryItem::isolatedEmoji() const {
|
||||||
|
return Ui::Text::IsolatedEmoji();
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryItem::drawInDialog(
|
void HistoryItem::drawInDialog(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
const QRect &r,
|
const QRect &r,
|
||||||
|
|
|
@ -163,8 +163,8 @@ public:
|
||||||
[[nodiscard]] bool isGroupMigrate() const {
|
[[nodiscard]] bool isGroupMigrate() const {
|
||||||
return isGroupEssential() && isEmpty();
|
return isGroupEssential() && isEmpty();
|
||||||
}
|
}
|
||||||
[[nodiscard]] bool isSingleEmoji() const {
|
[[nodiscard]] bool isIsolatedEmoji() const {
|
||||||
return _flags & MTPDmessage_ClientFlag::f_single_emoji;
|
return _flags & MTPDmessage_ClientFlag::f_isolated_emoji;
|
||||||
}
|
}
|
||||||
[[nodiscard]] bool hasViews() const {
|
[[nodiscard]] bool hasViews() const {
|
||||||
return _flags & MTPDmessage::Flag::f_views;
|
return _flags & MTPDmessage::Flag::f_views;
|
||||||
|
@ -226,9 +226,7 @@ public:
|
||||||
virtual QString inReplyText() const {
|
virtual QString inReplyText() const {
|
||||||
return inDialogsText(DrawInDialog::WithoutSender);
|
return inDialogsText(DrawInDialog::WithoutSender);
|
||||||
}
|
}
|
||||||
virtual QString originalString() const {
|
virtual Ui::Text::IsolatedEmoji isolatedEmoji() const;
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
virtual TextWithEntities originalText() const {
|
virtual TextWithEntities originalText() const {
|
||||||
return TextWithEntities();
|
return TextWithEntities();
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "boxes/confirm_box.h"
|
#include "boxes/confirm_box.h"
|
||||||
#include "ui/toast/toast.h"
|
#include "ui/toast/toast.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
|
#include "ui/text/text_isolated_emoji.h"
|
||||||
#include "ui/text_options.h"
|
#include "ui/text_options.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "layout.h"
|
#include "layout.h"
|
||||||
|
@ -1084,7 +1085,7 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSingleEmoji();
|
clearIsolatedEmoji();
|
||||||
if (_media && _media->consumeMessageText(textWithEntities)) {
|
if (_media && _media->consumeMessageText(textWithEntities)) {
|
||||||
setEmptyText();
|
setEmptyText();
|
||||||
} else {
|
} else {
|
||||||
|
@ -1100,7 +1101,7 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
|
||||||
{ QString::fromUtf8(":-("), EntitiesInText() },
|
{ QString::fromUtf8(":-("), EntitiesInText() },
|
||||||
Ui::ItemTextOptions(this));
|
Ui::ItemTextOptions(this));
|
||||||
} else if (!_media) {
|
} else if (!_media) {
|
||||||
checkSingleEmoji(textWithEntities.text);
|
checkIsolatedEmoji();
|
||||||
}
|
}
|
||||||
_textWidth = -1;
|
_textWidth = -1;
|
||||||
_textHeight = 0;
|
_textHeight = 0;
|
||||||
|
@ -1117,17 +1118,17 @@ void HistoryMessage::setEmptyText() {
|
||||||
_textHeight = 0;
|
_textHeight = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryMessage::clearSingleEmoji() {
|
void HistoryMessage::clearIsolatedEmoji() {
|
||||||
if (!(_flags & MTPDmessage_ClientFlag::f_single_emoji)) {
|
if (!(_flags & MTPDmessage_ClientFlag::f_isolated_emoji)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
history()->session().emojiStickersPack().remove(this);
|
history()->session().emojiStickersPack().remove(this);
|
||||||
_flags &= ~MTPDmessage_ClientFlag::f_single_emoji;
|
_flags &= ~MTPDmessage_ClientFlag::f_isolated_emoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryMessage::checkSingleEmoji(const QString &text) {
|
void HistoryMessage::checkIsolatedEmoji() {
|
||||||
if (history()->session().emojiStickersPack().add(this, text)) {
|
if (history()->session().emojiStickersPack().add(this)) {
|
||||||
_flags |= MTPDmessage_ClientFlag::f_single_emoji;
|
_flags |= MTPDmessage_ClientFlag::f_isolated_emoji;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1173,8 +1174,8 @@ void HistoryMessage::setReplyMarkup(const MTPReplyMarkup *markup) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString HistoryMessage::originalString() const {
|
Ui::Text::IsolatedEmoji HistoryMessage::isolatedEmoji() const {
|
||||||
return emptyText() ? QString() : _text.toString();
|
return _text.toIsolatedEmoji();
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities HistoryMessage::originalText() const {
|
TextWithEntities HistoryMessage::originalText() const {
|
||||||
|
|
|
@ -133,7 +133,7 @@ public:
|
||||||
[[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const override;
|
[[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const override;
|
||||||
|
|
||||||
void setText(const TextWithEntities &textWithEntities) 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]] TextWithEntities originalText() const override;
|
||||||
[[nodiscard]] TextForMimeData clipboardText() const override;
|
[[nodiscard]] TextForMimeData clipboardText() const override;
|
||||||
[[nodiscard]] bool textHasLinks() const override;
|
[[nodiscard]] bool textHasLinks() const override;
|
||||||
|
@ -164,8 +164,8 @@ private:
|
||||||
return _flags & MTPDmessage::Flag::f_legacy;
|
return _flags & MTPDmessage::Flag::f_legacy;
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearSingleEmoji();
|
void clearIsolatedEmoji();
|
||||||
void checkSingleEmoji(const QString &text);
|
void checkIsolatedEmoji();
|
||||||
|
|
||||||
// For an invoice button we replace the button text with a "Receipt" key.
|
// 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.
|
// 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.h"
|
||||||
#include "history/view/media/history_view_media_grouped.h"
|
#include "history/view/media/history_view_media_grouped.h"
|
||||||
#include "history/view/media/history_view_sticker.h"
|
#include "history/view/media/history_view_sticker.h"
|
||||||
|
#include "history/view/media/history_view_large_emoji.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "chat_helpers/stickers_emoji_pack.h"
|
#include "chat_helpers/stickers_emoji_pack.h"
|
||||||
|
@ -341,13 +342,22 @@ void Element::refreshMedia() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto emojiStickers = &history()->session().emojiStickersPack();
|
const auto session = &history()->session();
|
||||||
if (_data->media()) {
|
if (const auto media = _data->media()) {
|
||||||
_media = _data->media()->createView(this);
|
_media = media->createView(this);
|
||||||
} else if (const auto document = emojiStickers->stickerForEmoji(_data)) {
|
} else if (_data->isIsolatedEmoji()
|
||||||
_media = std::make_unique<UnwrappedMedia>(
|
&& session->settings().largeEmoji()) {
|
||||||
this,
|
const auto emoji = _data->isolatedEmoji();
|
||||||
std::make_unique<StickerContent>(this, document));
|
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 {
|
} else {
|
||||||
_media = nullptr;
|
_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()) {
|
if (document->sticker()) {
|
||||||
return std::make_unique<UnwrappedMedia>(
|
return std::make_unique<UnwrappedMedia>(
|
||||||
parent,
|
parent,
|
||||||
std::make_unique<StickerContent>(parent, document));
|
std::make_unique<Sticker>(parent, document));
|
||||||
} else if (document->isAnimation()) {
|
} else if (document->isAnimation()) {
|
||||||
return std::make_unique<Gif>(parent, document);
|
return std::make_unique<Gif>(parent, document);
|
||||||
} else if (document->isVideoFile()) {
|
} else if (document->isVideoFile()) {
|
||||||
|
|
|
@ -31,8 +31,9 @@ QSize UnwrappedMedia::countOptimalSize() {
|
||||||
_contentSize = NonEmptySize(DownscaledSize(
|
_contentSize = NonEmptySize(DownscaledSize(
|
||||||
_content->size(),
|
_content->size(),
|
||||||
{ st::maxStickerSize, st::maxStickerSize }));
|
{ st::maxStickerSize, st::maxStickerSize }));
|
||||||
|
const auto minimal = st::largeEmojiSize;
|
||||||
auto maxWidth = std::max(_contentSize.width(), st::minPhotoSize);
|
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(
|
accumulate_max(
|
||||||
maxWidth,
|
maxWidth,
|
||||||
_parent->infoWidth() + 2 * st::msgDateImgPadding.x());
|
_parent->infoWidth() + 2 * st::msgDateImgPadding.x());
|
||||||
|
|
|
@ -22,8 +22,12 @@ public:
|
||||||
class Content {
|
class Content {
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] virtual QSize size() = 0;
|
[[nodiscard]] virtual QSize size() = 0;
|
||||||
|
|
||||||
virtual void draw(Painter &p, const QRect &r, bool selected) = 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() {
|
[[nodiscard]] virtual DocumentData *document() {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
|
@ -34,7 +34,7 @@ double GetEmojiStickerZoom(not_null<Main::Session*> session) {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
StickerContent::StickerContent(
|
Sticker::Sticker(
|
||||||
not_null<Element*> parent,
|
not_null<Element*> parent,
|
||||||
not_null<DocumentData*> document)
|
not_null<DocumentData*> document)
|
||||||
: _parent(parent)
|
: _parent(parent)
|
||||||
|
@ -42,15 +42,15 @@ StickerContent::StickerContent(
|
||||||
_document->loadThumbnail(parent->data()->fullId());
|
_document->loadThumbnail(parent->data()->fullId());
|
||||||
}
|
}
|
||||||
|
|
||||||
StickerContent::~StickerContent() {
|
Sticker::~Sticker() {
|
||||||
unloadLottie();
|
unloadLottie();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StickerContent::isEmojiSticker() const {
|
bool Sticker::isEmojiSticker() const {
|
||||||
return (_parent->data()->media() == nullptr);
|
return (_parent->data()->media() == nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize StickerContent::size() {
|
QSize Sticker::size() {
|
||||||
_size = _document->dimensions;
|
_size = _document->dimensions;
|
||||||
if (isEmojiSticker()) {
|
if (isEmojiSticker()) {
|
||||||
constexpr auto kIdealStickerSize = 512;
|
constexpr auto kIdealStickerSize = 512;
|
||||||
|
@ -63,10 +63,7 @@ QSize StickerContent::size() {
|
||||||
return _size;
|
return _size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StickerContent::draw(
|
void Sticker::draw(Painter &p, const QRect &r, bool selected) {
|
||||||
Painter &p,
|
|
||||||
const QRect &r,
|
|
||||||
bool selected) {
|
|
||||||
const auto sticker = _document->sticker();
|
const auto sticker = _document->sticker();
|
||||||
if (!sticker) {
|
if (!sticker) {
|
||||||
return;
|
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();
|
auto request = Lottie::FrameRequest();
|
||||||
request.box = _size * cIntRetinaFactor();
|
request.box = _size * cIntRetinaFactor();
|
||||||
if (selected) {
|
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);
|
const auto pixmap = paintedPixmap(selected);
|
||||||
if (!pixmap.isNull()) {
|
if (!pixmap.isNull()) {
|
||||||
p.drawPixmap(
|
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 o = _parent->data()->fullId();
|
||||||
const auto w = _size.width();
|
const auto w = _size.width();
|
||||||
const auto h = _size.height();
|
const auto h = _size.height();
|
||||||
|
@ -157,7 +154,7 @@ QPixmap StickerContent::paintedPixmap(bool selected) const {
|
||||||
return QPixmap();
|
return QPixmap();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StickerContent::refreshLink() {
|
void Sticker::refreshLink() {
|
||||||
if (_link) {
|
if (_link) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -180,7 +177,7 @@ void StickerContent::refreshLink() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StickerContent::setupLottie() {
|
void Sticker::setupLottie() {
|
||||||
_lottie = Stickers::LottiePlayerFromDocument(
|
_lottie = Stickers::LottiePlayerFromDocument(
|
||||||
_document,
|
_document,
|
||||||
Stickers::LottieSize::MessageHistory,
|
Stickers::LottieSize::MessageHistory,
|
||||||
|
@ -198,7 +195,7 @@ void StickerContent::setupLottie() {
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StickerContent::unloadLottie() {
|
void Sticker::unloadLottie() {
|
||||||
if (!_lottie) {
|
if (!_lottie) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "history/view/media/history_view_media_unwrapped.h"
|
#include "history/view/media/history_view_media_unwrapped.h"
|
||||||
#include "base/weak_ptr.h"
|
#include "base/weak_ptr.h"
|
||||||
#include "base/timer.h"
|
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
struct FileOrigin;
|
struct FileOrigin;
|
||||||
|
@ -21,14 +20,14 @@ class SinglePlayer;
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
class StickerContent final
|
class Sticker final
|
||||||
: public UnwrappedMedia::Content
|
: public UnwrappedMedia::Content
|
||||||
, public base::has_weak_ptr {
|
, public base::has_weak_ptr {
|
||||||
public:
|
public:
|
||||||
StickerContent(
|
Sticker(
|
||||||
not_null<Element*> parent,
|
not_null<Element*> parent,
|
||||||
not_null<DocumentData*> document);
|
not_null<DocumentData*> document);
|
||||||
~StickerContent();
|
~Sticker();
|
||||||
|
|
||||||
QSize size() override;
|
QSize size() override;
|
||||||
void draw(Painter &p, const QRect &r, bool selected) 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
|
// message was an outgoing message and failed to be sent
|
||||||
f_failed = (1U << 22),
|
f_failed = (1U << 22),
|
||||||
|
|
||||||
// message has no media and only a single emoji text
|
// message has no media and only a several emoji text
|
||||||
f_single_emoji = (1U << 21),
|
f_isolated_emoji = (1U << 21),
|
||||||
|
|
||||||
// update this when adding new client side flags
|
// update this when adding new client side flags
|
||||||
MIN_FIELD = (1U << 21),
|
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 SizeNormal = -1;
|
||||||
auto SizeLarge = -1;
|
auto SizeLarge = -1;
|
||||||
auto SpritesCount = -1;
|
auto SpritesCount = -1;
|
||||||
|
@ -359,6 +339,71 @@ std::vector<QImage> LoadAndValidateSprites(int id) {
|
||||||
return result;
|
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) {
|
UniversalImages::UniversalImages(int id) : _id(id) {
|
||||||
Expects(IsValidSetId(id));
|
Expects(IsValidSetId(id));
|
||||||
}
|
}
|
||||||
|
@ -452,71 +497,6 @@ QImage UniversalImages::generate(int size, int index) const {
|
||||||
return result;
|
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() {
|
void Init() {
|
||||||
internal::Init();
|
internal::Init();
|
||||||
|
|
||||||
|
|
|
@ -167,5 +167,25 @@ rpl::producer<> UpdatedRecent();
|
||||||
const QPixmap &SinglePixmap(EmojiPtr emoji, int fontHeight);
|
const QPixmap &SinglePixmap(EmojiPtr emoji, int fontHeight);
|
||||||
void Draw(QPainter &p, EmojiPtr emoji, int size, int x, int y);
|
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 Emoji
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "core/click_handler_types.h"
|
#include "core/click_handler_types.h"
|
||||||
#include "core/crash_reports.h"
|
#include "core/crash_reports.h"
|
||||||
#include "ui/text/text_block.h"
|
#include "ui/text/text_block.h"
|
||||||
|
#include "ui/text/text_isolated_emoji.h"
|
||||||
#include "ui/emoji_config.h"
|
#include "ui/emoji_config.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "platform/platform_info.h"
|
#include "platform/platform_info.h"
|
||||||
|
@ -3311,6 +3312,25 @@ TextForMimeData String::toText(
|
||||||
return result;
|
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() {
|
void String::clear() {
|
||||||
clearFields();
|
clearFields();
|
||||||
_text.clear();
|
_text.clear();
|
||||||
|
|
|
@ -7,8 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/click_handler.h"
|
|
||||||
#include "ui/text/text_entity.h"
|
#include "ui/text/text_entity.h"
|
||||||
|
#include "core/click_handler.h"
|
||||||
#include "base/flags.h"
|
#include "base/flags.h"
|
||||||
|
|
||||||
#include <private/qfixed_p.h>
|
#include <private/qfixed_p.h>
|
||||||
|
@ -70,6 +70,7 @@ namespace Ui {
|
||||||
namespace Text {
|
namespace Text {
|
||||||
|
|
||||||
class AbstractBlock;
|
class AbstractBlock;
|
||||||
|
struct IsolatedEmoji;
|
||||||
|
|
||||||
struct StateRequest {
|
struct StateRequest {
|
||||||
enum class Flag {
|
enum class Flag {
|
||||||
|
@ -176,6 +177,7 @@ public:
|
||||||
TextSelection selection = AllTextSelection) const;
|
TextSelection selection = AllTextSelection) const;
|
||||||
TextForMimeData toTextForMimeData(
|
TextForMimeData toTextForMimeData(
|
||||||
TextSelection selection = AllTextSelection) const;
|
TextSelection selection = AllTextSelection) const;
|
||||||
|
IsolatedEmoji toIsolatedEmoji() const;
|
||||||
|
|
||||||
bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation
|
bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation
|
||||||
if (_text.size() < maxdots) return false;
|
if (_text.size() < maxdots) return false;
|
||||||
|
|
|
@ -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_gif.cpp
|
||||||
<(src_loc)/history/view/media/history_view_invoice.h
|
<(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_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.h
|
||||||
<(src_loc)/history/view/media/history_view_location.cpp
|
<(src_loc)/history/view/media/history_view_location.cpp
|
||||||
<(src_loc)/history/view/media/history_view_media.h
|
<(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_block.h
|
||||||
<(src_loc)/ui/text/text_entity.cpp
|
<(src_loc)/ui/text/text_entity.cpp
|
||||||
<(src_loc)/ui/text/text_entity.h
|
<(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.cpp
|
||||||
<(src_loc)/ui/text/text_utilities.h
|
<(src_loc)/ui/text/text_utilities.h
|
||||||
<(src_loc)/ui/toast/toast.cpp
|
<(src_loc)/ui/toast/toast.cpp
|
||||||
|
|
Loading…
Reference in New Issue