Add large emoji implementation.

This commit is contained in:
John Preston 2019-08-02 19:19:14 +01:00
parent 1d52ba7a42
commit d298953653
24 changed files with 690 additions and 175 deletions

View File

@ -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();
});
}

View File

@ -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;
};

View File

@ -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()) {

View File

@ -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;

View File

@ -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);

View File

@ -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,

View File

@ -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();
}

View File

@ -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 {

View File

@ -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.

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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()) {

View File

@ -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());

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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),

View File

@ -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();

View File

@ -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

View File

@ -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();

View File

@ -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;

View 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

View File

@ -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