data_custom_emoji.cpp 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "data/stickers/data_custom_emoji.h"
  8. #include "boxes/peers/edit_forum_topic_box.h" // MakeTopicIconEmoji.
  9. #include "chat_helpers/stickers_emoji_pack.h"
  10. #include "main/main_app_config.h"
  11. #include "main/main_session.h"
  12. #include "data/data_channel.h"
  13. #include "data/data_session.h"
  14. #include "data/data_document.h"
  15. #include "data/data_document_media.h"
  16. #include "data/data_emoji_statuses.h"
  17. #include "data/data_file_origin.h"
  18. #include "data/data_forum_topic.h" // ParseTopicIconEmojiEntity.
  19. #include "data/data_peer.h"
  20. #include "data/data_message_reactions.h"
  21. #include "data/stickers/data_stickers.h"
  22. #include "dialogs/ui/dialogs_stories_content.h"
  23. #include "dialogs/ui/dialogs_stories_content.h"
  24. #include "lottie/lottie_common.h"
  25. #include "lottie/lottie_frame_generator.h"
  26. #include "ffmpeg/ffmpeg_frame_generator.h"
  27. #include "chat_helpers/stickers_lottie.h"
  28. #include "storage/file_download.h" // kMaxFileInMemory
  29. #include "ui/chat/chats_filter_tag.h"
  30. #include "ui/effects/premium_stars_colored.h"
  31. #include "ui/effects/credits_graphics.h"
  32. #include "ui/widgets/fields/input_field.h"
  33. #include "ui/text/custom_emoji_instance.h"
  34. #include "ui/text/text_custom_emoji.h"
  35. #include "ui/text/text_utilities.h"
  36. #include "ui/dynamic_thumbnails.h"
  37. #include "ui/ui_utility.h"
  38. #include "apiwrap.h"
  39. #include "styles/style_chat.h"
  40. #include "styles/style_chat_helpers.h"
  41. #include "styles/style_credits.h" // giftBoxByStarsStyle
  42. namespace Data {
  43. namespace {
  44. constexpr auto kMaxPerRequest = 100;
  45. #if 0 // inject-to-on_main
  46. constexpr auto kUnsubscribeUpdatesDelay = 3 * crl::time(1000);
  47. #endif
  48. using SizeTag = CustomEmojiManager::SizeTag;
  49. class CallbackListener final : public CustomEmojiManager::Listener {
  50. public:
  51. explicit CallbackListener(Fn<void(not_null<DocumentData*>)> callback)
  52. : _callback(std::move(callback)) {
  53. Expects(_callback != nullptr);
  54. }
  55. private:
  56. void customEmojiResolveDone(not_null<DocumentData*> document) {
  57. _callback(document);
  58. }
  59. Fn<void(not_null<DocumentData*>)> _callback;
  60. };
  61. [[nodiscard]] ChatHelpers::StickerLottieSize LottieSizeFromTag(SizeTag tag) {
  62. // NB! onlyCustomEmoji dimensions caching uses last ::EmojiInteraction-s.
  63. using LottieSize = ChatHelpers::StickerLottieSize;
  64. switch (tag) {
  65. case SizeTag::Normal: return LottieSize::EmojiInteraction;
  66. case SizeTag::Large: return LottieSize::EmojiInteractionReserved1;
  67. case SizeTag::Isolated: return LottieSize::EmojiInteractionReserved2;
  68. case SizeTag::SetIcon: return LottieSize::EmojiInteractionReserved3;
  69. }
  70. Unexpected("SizeTag value in CustomEmojiManager-LottieSizeFromTag.");
  71. }
  72. [[nodiscard]] int EmojiSizeFromTag(SizeTag tag) {
  73. switch (tag) {
  74. case SizeTag::Normal: return Ui::Emoji::GetSizeNormal();
  75. case SizeTag::Large: return Ui::Emoji::GetSizeLarge();
  76. case SizeTag::Isolated:
  77. return (st::largeEmojiSize + 2 * st::largeEmojiOutline)
  78. * style::DevicePixelRatio();
  79. case SizeTag::SetIcon:
  80. return int(style::ConvertScale(18 * 7 / 6., style::Scale()))
  81. * style::DevicePixelRatio();
  82. }
  83. Unexpected("SizeTag value in CustomEmojiManager-SizeFromTag.");
  84. }
  85. [[nodiscard]] int FrameSizeFromTag(SizeTag tag, int sizeOverride) {
  86. return sizeOverride
  87. ? (sizeOverride * style::DevicePixelRatio())
  88. : FrameSizeFromTag(tag);
  89. }
  90. [[nodiscard]] QString InternalPrefix() {
  91. return u"internal:"_q;
  92. }
  93. [[nodiscard]] QString UserpicEmojiPrefix() {
  94. return u"userpic:"_q;
  95. }
  96. [[nodiscard]] QString ScaledSimplePrefix() {
  97. return u"scaled-simple:"_q;
  98. }
  99. [[nodiscard]] QString ScaledCustomPrefix() {
  100. return u"scaled-custom:"_q;
  101. }
  102. [[nodiscard]] QString ForceStaticPrefix() {
  103. return u"force-static:"_q;
  104. }
  105. [[nodiscard]] QString CollectiblePrefix() {
  106. return u"collectible:"_q;
  107. }
  108. [[nodiscard]] QString InternalPadding(QMargins value) {
  109. return value.isNull() ? QString() : QString(",%1,%2,%3,%4"
  110. ).arg(value.left()
  111. ).arg(value.top()
  112. ).arg(value.right()
  113. ).arg(value.bottom());
  114. }
  115. } // namespace
  116. class CustomEmojiLoader final
  117. : public Ui::CustomEmoji::Loader
  118. , public base::has_weak_ptr {
  119. public:
  120. CustomEmojiLoader(
  121. not_null<Session*> owner,
  122. DocumentId id,
  123. SizeTag tag,
  124. int sizeOverride);
  125. CustomEmojiLoader(
  126. not_null<DocumentData*> document,
  127. SizeTag tag,
  128. int sizeOverride);
  129. [[nodiscard]] DocumentData *document() const;
  130. void resolved(not_null<DocumentData*> document);
  131. QString entityData() override;
  132. void load(Fn<void(LoadResult)> loaded) override;
  133. bool loading() override;
  134. void cancel() override;
  135. Ui::CustomEmoji::Preview preview() override;
  136. private:
  137. struct Resolve {
  138. Fn<void(LoadResult)> requested;
  139. QString entityData;
  140. };
  141. struct Process {
  142. std::shared_ptr<DocumentMedia> media;
  143. Fn<void(LoadResult)> loaded;
  144. base::has_weak_ptr guard;
  145. rpl::lifetime lifetime;
  146. };
  147. struct Requested {
  148. not_null<DocumentData*> document;
  149. std::unique_ptr<Process> process;
  150. };
  151. struct Lookup : Requested {
  152. };
  153. struct Load : Requested {
  154. };
  155. void check();
  156. [[nodiscard]] Storage::Cache::Key cacheKey(
  157. not_null<DocumentData*> document) const;
  158. void startCacheLookup(
  159. not_null<Lookup*> lookup,
  160. Fn<void(LoadResult)> loaded);
  161. void lookupDone(
  162. not_null<Lookup*> lookup,
  163. std::optional<Ui::CustomEmoji::Cache> result);
  164. void loadNoCache(
  165. not_null<DocumentData*> document,
  166. Fn<void(LoadResult)> loaded);
  167. [[nodiscard]] static std::variant<Resolve, Lookup, Load> InitialState(
  168. not_null<Session*> owner,
  169. DocumentId id);
  170. std::variant<Resolve, Lookup, Load> _state;
  171. ushort _sizeOverride = 0;
  172. SizeTag _tag = SizeTag::Normal;
  173. };
  174. CustomEmojiLoader::CustomEmojiLoader(
  175. not_null<Session*> owner,
  176. DocumentId id,
  177. SizeTag tag,
  178. int sizeOverride)
  179. : _state(InitialState(owner, id))
  180. , _sizeOverride(sizeOverride)
  181. , _tag(tag) {
  182. Expects(sizeOverride >= 0
  183. && sizeOverride <= std::numeric_limits<ushort>::max());
  184. }
  185. CustomEmojiLoader::CustomEmojiLoader(
  186. not_null<DocumentData*> document,
  187. SizeTag tag,
  188. int sizeOverride)
  189. : _state(Lookup{ document })
  190. , _sizeOverride(sizeOverride)
  191. , _tag(tag) {
  192. Expects(sizeOverride >= 0
  193. && sizeOverride <= std::numeric_limits<ushort>::max());
  194. }
  195. DocumentData *CustomEmojiLoader::document() const {
  196. return v::match(_state, [](const Resolve &) {
  197. return (DocumentData*)nullptr;
  198. }, [](const auto &data) {
  199. return data.document.get();
  200. });
  201. }
  202. void CustomEmojiLoader::resolved(not_null<DocumentData*> document) {
  203. Expects(v::is<Resolve>(_state));
  204. auto requested = std::move(v::get<Resolve>(_state).requested);
  205. _state = Lookup{ document };
  206. if (requested) {
  207. load(std::move(requested));
  208. }
  209. }
  210. void CustomEmojiLoader::load(Fn<void(LoadResult)> loaded) {
  211. if (const auto resolve = std::get_if<Resolve>(&_state)) {
  212. resolve->requested = std::move(loaded);
  213. } else if (const auto lookup = std::get_if<Lookup>(&_state)) {
  214. if (!lookup->process) {
  215. startCacheLookup(lookup, std::move(loaded));
  216. } else {
  217. lookup->process->loaded = std::move(loaded);
  218. }
  219. } else if (const auto load = std::get_if<Load>(&_state)) {
  220. if (!load->process) {
  221. load->process = std::make_unique<Process>(Process{
  222. .media = load->document->createMediaView(),
  223. .loaded = std::move(loaded),
  224. });
  225. load->process->media->owner()->resetCancelled();
  226. load->process->media->checkStickerLarge();
  227. if (load->process->media->loaded()) {
  228. check();
  229. } else {
  230. load->document->session().downloaderTaskFinished(
  231. ) | rpl::start_with_next([=] {
  232. check();
  233. }, load->process->lifetime);
  234. }
  235. } else {
  236. load->process->loaded = std::move(loaded);
  237. }
  238. }
  239. }
  240. QString CustomEmojiLoader::entityData() {
  241. if (const auto resolve = std::get_if<Resolve>(&_state)) {
  242. return resolve->entityData;
  243. } else if (const auto lookup = std::get_if<Lookup>(&_state)) {
  244. return SerializeCustomEmojiId(lookup->document);
  245. } else if (const auto load = std::get_if<Load>(&_state)) {
  246. return SerializeCustomEmojiId(load->document);
  247. }
  248. Unexpected("State in CustomEmojiLoader::entityData.");
  249. }
  250. bool CustomEmojiLoader::loading() {
  251. if (const auto resolve = std::get_if<Resolve>(&_state)) {
  252. return (resolve->requested != nullptr);
  253. } else if (const auto lookup = std::get_if<Lookup>(&_state)) {
  254. return (lookup->process != nullptr);
  255. } else if (const auto load = std::get_if<Load>(&_state)) {
  256. return (load->process != nullptr);
  257. }
  258. return false;
  259. }
  260. Storage::Cache::Key CustomEmojiLoader::cacheKey(
  261. not_null<DocumentData*> document) const {
  262. const auto baseKey = document->bigFileBaseCacheKey();
  263. if (!baseKey) {
  264. return {};
  265. }
  266. return Storage::Cache::Key{
  267. baseKey.high,
  268. baseKey.low + ChatHelpers::LottieCacheKeyShift(
  269. 0x0F,
  270. LottieSizeFromTag(_tag)),
  271. };
  272. }
  273. void CustomEmojiLoader::startCacheLookup(
  274. not_null<Lookup*> lookup,
  275. Fn<void(LoadResult)> loaded) {
  276. const auto document = lookup->document;
  277. const auto key = cacheKey(document);
  278. if (!key) {
  279. loadNoCache(document, std::move(loaded));
  280. return;
  281. }
  282. lookup->process = std::make_unique<Process>(Process{
  283. .loaded = std::move(loaded),
  284. });
  285. const auto size = FrameSizeFromTag(_tag, _sizeOverride);
  286. const auto weak = base::make_weak(&lookup->process->guard);
  287. document->owner().cacheBigFile().get(key, [=](QByteArray value) {
  288. auto cache = Ui::CustomEmoji::Cache::FromSerialized(value, size);
  289. crl::on_main(weak, [=, result = std::move(cache)]() mutable {
  290. lookupDone(lookup, std::move(result));
  291. });
  292. });
  293. }
  294. void CustomEmojiLoader::lookupDone(
  295. not_null<Lookup*> lookup,
  296. std::optional<Ui::CustomEmoji::Cache> result) {
  297. const auto document = lookup->document;
  298. if (!result) {
  299. loadNoCache(document, std::move(lookup->process->loaded));
  300. return;
  301. }
  302. const auto tag = _tag;
  303. const auto sizeOverride = int(_sizeOverride);
  304. auto loader = [=] {
  305. return std::make_unique<CustomEmojiLoader>(
  306. document,
  307. tag,
  308. sizeOverride);
  309. };
  310. auto done = std::move(lookup->process->loaded);
  311. done(Ui::CustomEmoji::Cached(
  312. SerializeCustomEmojiId(document),
  313. std::move(loader),
  314. std::move(*result)));
  315. }
  316. void CustomEmojiLoader::loadNoCache(
  317. not_null<DocumentData*> document,
  318. Fn<void(LoadResult)> loaded) {
  319. _state = Load{ document };
  320. load(std::move(loaded));
  321. }
  322. void CustomEmojiLoader::check() {
  323. using namespace Ui::CustomEmoji;
  324. const auto load = std::get_if<Load>(&_state);
  325. Assert(load != nullptr);
  326. Assert(load->process != nullptr);
  327. const auto media = load->process->media.get();
  328. const auto document = media->owner();
  329. const auto data = media->bytes();
  330. const auto filepath = document->filepath();
  331. if (data.isEmpty() && filepath.isEmpty()) {
  332. return;
  333. }
  334. load->process->lifetime.destroy();
  335. const auto tag = _tag;
  336. const auto sizeOverride = int(_sizeOverride);
  337. const auto size = FrameSizeFromTag(_tag, _sizeOverride);
  338. auto loader = [=] {
  339. return std::make_unique<CustomEmojiLoader>(
  340. document,
  341. tag,
  342. sizeOverride);
  343. };
  344. auto put = [=, key = cacheKey(document)](QByteArray value) {
  345. const auto size = value.size();
  346. if (size <= Storage::kMaxFileInMemory) {
  347. document->owner().cacheBigFile().put(key, std::move(value));
  348. } else {
  349. LOG(("Data Error: Cached emoji size too big: %1.").arg(size));
  350. }
  351. };
  352. const auto type = document->sticker()->type;
  353. auto generator = [=, bytes = Lottie::ReadContent(data, filepath)]()
  354. -> std::unique_ptr<Ui::FrameGenerator> {
  355. switch (type) {
  356. case StickerType::Tgs:
  357. return std::make_unique<Lottie::FrameGenerator>(bytes);
  358. case StickerType::Webm:
  359. return std::make_unique<FFmpeg::FrameGenerator>(bytes);
  360. case StickerType::Webp:
  361. return std::make_unique<Ui::ImageFrameGenerator>(bytes);
  362. }
  363. Unexpected("Type in custom emoji sticker frame generator.");
  364. };
  365. auto renderer = std::make_unique<Renderer>(RendererDescriptor{
  366. .generator = std::move(generator),
  367. .put = std::move(put),
  368. .loader = std::move(loader),
  369. .size = size,
  370. });
  371. base::take(load->process)->loaded(Caching{
  372. std::move(renderer),
  373. SerializeCustomEmojiId(document),
  374. });
  375. }
  376. auto CustomEmojiLoader::InitialState(
  377. not_null<Session*> owner,
  378. DocumentId id)
  379. -> std::variant<Resolve, Lookup, Load> {
  380. const auto document = owner->document(id);
  381. if (document->sticker()) {
  382. return Lookup{ document };
  383. }
  384. return Resolve{ .entityData = SerializeCustomEmojiId(id) };
  385. }
  386. void CustomEmojiLoader::cancel() {
  387. if (const auto lookup = std::get_if<Lookup>(&_state)) {
  388. base::take(lookup->process);
  389. } else if (const auto load = std::get_if<Load>(&_state)) {
  390. if (base::take(load->process)) {
  391. load->document->cancel();
  392. }
  393. }
  394. }
  395. Ui::CustomEmoji::Preview CustomEmojiLoader::preview() {
  396. using Preview = Ui::CustomEmoji::Preview;
  397. const auto make = [&](not_null<DocumentData*> document) -> Preview {
  398. const auto dimensions = document->dimensions;
  399. if (!document->inlineThumbnailIsPath()
  400. || dimensions.isEmpty()) {
  401. return {};
  402. }
  403. const auto scale = (FrameSizeFromTag(_tag, _sizeOverride) * 1.)
  404. / (style::DevicePixelRatio() * dimensions.width());
  405. return { document->createMediaView()->thumbnailPath(), scale };
  406. };
  407. if (const auto lookup = std::get_if<Lookup>(&_state)) {
  408. return make(lookup->document);
  409. } else if (const auto load = std::get_if<Load>(&_state)) {
  410. return make(load->document);
  411. }
  412. return {};
  413. }
  414. CustomEmojiManager::CustomEmojiManager(not_null<Session*> owner)
  415. : _owner(owner)
  416. , _repaintTimer([=] { invokeRepaints(); }) {
  417. const auto appConfig = &owner->session().appConfig();
  418. appConfig->value(
  419. ) | rpl::take_while([=] {
  420. return !_coloredSetId;
  421. }) | rpl::start_with_next([=] {
  422. const auto setId = appConfig->get<QString>(
  423. "default_emoji_statuses_stickerset_id",
  424. QString()).toULongLong();
  425. if (setId) {
  426. _coloredSetId = setId;
  427. }
  428. }, _lifetime);
  429. }
  430. CustomEmojiManager::~CustomEmojiManager() = default;
  431. template <typename LoaderFactory>
  432. std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
  433. DocumentId documentId,
  434. Fn<void()> update,
  435. SizeTag tag,
  436. int sizeOverride,
  437. LoaderFactory factory) {
  438. auto &instances = _instances[SizeIndex(tag)];
  439. auto i = instances.find(documentId);
  440. if (i == end(instances)) {
  441. using Loading = Ui::CustomEmoji::Loading;
  442. const auto repaint = [=](
  443. not_null<Ui::CustomEmoji::Instance*> instance,
  444. Ui::CustomEmoji::RepaintRequest request) {
  445. repaintLater(instance, request);
  446. };
  447. auto [loader, setId, colored] = factory();
  448. i = instances.emplace(
  449. documentId,
  450. std::make_unique<Ui::CustomEmoji::Instance>(Loading{
  451. std::move(loader),
  452. prepareNonExactPreview(documentId, tag, sizeOverride)
  453. }, std::move(repaint))).first;
  454. if (colored) {
  455. i->second->setColored();
  456. }
  457. } else if (!i->second->hasImagePreview()) {
  458. auto preview = prepareNonExactPreview(documentId, tag, sizeOverride);
  459. if (preview.isImage()) {
  460. i->second->updatePreview(std::move(preview));
  461. }
  462. }
  463. return std::make_unique<Ui::CustomEmoji::Object>(
  464. i->second.get(),
  465. std::move(update));
  466. }
  467. Ui::Text::CustomEmojiFactory CustomEmojiManager::factory(
  468. SizeTag tag,
  469. int sizeOverride) {
  470. return [=](QStringView data, const Ui::Text::MarkedContext &context) {
  471. return create(data, context.repaint, tag, sizeOverride);
  472. };
  473. }
  474. Ui::CustomEmoji::Preview CustomEmojiManager::prepareNonExactPreview(
  475. DocumentId documentId,
  476. SizeTag tag,
  477. int sizeOverride) const {
  478. for (auto i = _instances.size(); i != 0;) {
  479. if (SizeIndex(tag) == --i) {
  480. continue;
  481. }
  482. const auto &other = _instances[i];
  483. const auto j = other.find(documentId);
  484. if (j == end(other)) {
  485. continue;
  486. } else if (const auto nonExact = j->second->imagePreview()) {
  487. const auto size = FrameSizeFromTag(tag, sizeOverride);
  488. return {
  489. nonExact.image().scaled(
  490. size,
  491. size,
  492. Qt::IgnoreAspectRatio,
  493. Qt::SmoothTransformation),
  494. false,
  495. };
  496. }
  497. }
  498. return {};
  499. }
  500. std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
  501. QStringView data,
  502. Fn<void()> update,
  503. SizeTag tag,
  504. int sizeOverride) {
  505. if (data.startsWith(ScaledSimplePrefix())) {
  506. const auto text = data.mid(ScaledSimplePrefix().size());
  507. const auto emoji = Ui::Emoji::Find(text);
  508. Assert(emoji != nullptr);
  509. return Ui::MakeScaledSimpleEmoji(emoji);
  510. } else if (data.startsWith(ScaledCustomPrefix())) {
  511. const auto original = data.mid(ScaledCustomPrefix().size());
  512. return Ui::MakeScaledCustomEmoji(
  513. create(original, std::move(update), SizeTag::Large));
  514. } else if (data.startsWith(ForceStaticPrefix())) {
  515. const auto original = data.mid(ForceStaticPrefix().size());
  516. return std::make_unique<Ui::Text::FirstFrameEmoji>(
  517. create(original, std::move(update), tag, sizeOverride));
  518. } else if (data.startsWith(InternalPrefix())) {
  519. return internal(data);
  520. } else if (data.startsWith(UserpicEmojiPrefix())) {
  521. const auto ratio = style::DevicePixelRatio();
  522. const auto size = EmojiSizeFromTag(tag) / ratio;
  523. return userpic(data, std::move(update), size);
  524. } else if (data.startsWith(CollectiblePrefix())) {
  525. const auto id = data.mid(CollectiblePrefix().size()).toULongLong();
  526. const auto emojiStatuses = &session().data().emojiStatuses();
  527. auto info = emojiStatuses->collectibleInfo(id);
  528. Assert(info != nullptr);
  529. const auto documentId = info->documentId;
  530. auto inner = create(documentId, base::duplicate(update), tag);
  531. return Ui::Premium::MakeCollectibleEmoji(
  532. data,
  533. info->centerColor,
  534. info->edgeColor,
  535. std::move(inner),
  536. std::move(update),
  537. FrameSizeFromTag(tag) / style::DevicePixelRatio());
  538. } else if (const auto parsed = Data::ParseTopicIconEmojiEntity(data)) {
  539. return MakeTopicIconEmoji(parsed, std::move(update), tag);
  540. }
  541. const auto parsed = ParseCustomEmojiData(data);
  542. return parsed
  543. ? create(parsed, std::move(update), tag, sizeOverride)
  544. : nullptr;
  545. }
  546. std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
  547. DocumentId documentId,
  548. Fn<void()> update,
  549. SizeTag tag,
  550. int sizeOverride) {
  551. return create(documentId, std::move(update), tag, sizeOverride, [&] {
  552. return createLoaderWithSetId(documentId, tag, sizeOverride);
  553. });
  554. }
  555. std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
  556. not_null<DocumentData*> document,
  557. Fn<void()> update,
  558. SizeTag tag,
  559. int sizeOverride) {
  560. return create(document->id, std::move(update), tag, sizeOverride, [&] {
  561. return createLoaderWithSetId(document, tag, sizeOverride);
  562. });
  563. }
  564. std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::internal(
  565. QStringView data) {
  566. const auto v = data.mid(InternalPrefix().size()).split(',');
  567. if (v.size() != 5 && v.size() != 1) {
  568. return nullptr;
  569. }
  570. const auto index = v[0].toInt();
  571. Assert(index >= 0 && index < _internalEmoji.size());
  572. auto &info = _internalEmoji[index];
  573. const auto padding = (v.size() == 5)
  574. ? QMargins(v[1].toInt(), v[2].toInt(), v[3].toInt(), v[4].toInt())
  575. : QMargins();
  576. return std::make_unique<Ui::CustomEmoji::Internal>(
  577. data.toString(),
  578. info.image,
  579. padding,
  580. info.textColor);
  581. }
  582. std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::userpic(
  583. QStringView data,
  584. Fn<void()> update,
  585. int size) {
  586. const auto v = data.mid(UserpicEmojiPrefix().size()).split(',');
  587. if (v.size() != 5 && v.size() != 1) {
  588. return nullptr;
  589. }
  590. auto image = std::shared_ptr<Ui::DynamicImage>();
  591. if (v[0] == u"self"_q) {
  592. image = Ui::MakeSavedMessagesThumbnail();
  593. } else if (v[0] == u"replies"_q) {
  594. image = Ui::MakeRepliesThumbnail();
  595. } else {
  596. const auto id = PeerId(v[0].toULongLong());
  597. image = Ui::MakeUserpicThumbnail(_owner->peer(id));
  598. }
  599. const auto padding = (v.size() == 5)
  600. ? QMargins(v[1].toInt(), v[2].toInt(), v[3].toInt(), v[4].toInt())
  601. : QMargins();
  602. return std::make_unique<Ui::CustomEmoji::DynamicImageEmoji>(
  603. data.toString(),
  604. std::move(image),
  605. std::move(update),
  606. padding,
  607. size);
  608. }
  609. void CustomEmojiManager::resolve(
  610. QStringView data,
  611. not_null<Listener*> listener) {
  612. resolve(ParseCustomEmojiData(data), listener);
  613. }
  614. void CustomEmojiManager::resolve(
  615. DocumentId documentId,
  616. not_null<Listener*> listener) {
  617. if (_owner->document(documentId)->sticker()) {
  618. return;
  619. }
  620. _resolvers[documentId].emplace(listener);
  621. _listeners[listener].emplace(documentId);
  622. _pendingForRequest.emplace(documentId);
  623. if (!_requestId && _pendingForRequest.size() == 1) {
  624. crl::on_main(this, [=] { request(); });
  625. }
  626. }
  627. void CustomEmojiManager::unregisterListener(not_null<Listener*> listener) {
  628. if (const auto list = _listeners.take(listener)) {
  629. for (const auto id : *list) {
  630. const auto i = _resolvers.find(id);
  631. if (i != end(_resolvers)
  632. && i->second.remove(listener)
  633. && i->second.empty()) {
  634. _resolvers.erase(i);
  635. }
  636. }
  637. }
  638. }
  639. auto CustomEmojiManager::resolve(DocumentId documentId)
  640. -> rpl::producer<not_null<DocumentData*>, rpl::empty_error> {
  641. return [=](auto consumer) {
  642. auto result = rpl::lifetime();
  643. const auto put = [=](
  644. not_null<DocumentData*> document,
  645. bool resolved = true) {
  646. if (!document->sticker()) {
  647. if (resolved) {
  648. consumer.put_error({});
  649. }
  650. return false;
  651. }
  652. consumer.put_next_copy(document);
  653. return true;
  654. };
  655. if (!put(owner().document(documentId), false)) {
  656. const auto listener = result.make_state<CallbackListener>(put);
  657. resolve(documentId, listener);
  658. result.add([=] {
  659. unregisterListener(listener);
  660. });
  661. }
  662. return result;
  663. };
  664. }
  665. std::unique_ptr<Ui::CustomEmoji::Loader> CustomEmojiManager::createLoader(
  666. not_null<DocumentData*> document,
  667. SizeTag tag,
  668. int sizeOverride) {
  669. return createLoaderWithSetId(document, tag, sizeOverride).loader;
  670. }
  671. std::unique_ptr<Ui::CustomEmoji::Loader> CustomEmojiManager::createLoader(
  672. DocumentId documentId,
  673. SizeTag tag,
  674. int sizeOverride) {
  675. return createLoaderWithSetId(documentId, tag, sizeOverride).loader;
  676. }
  677. auto CustomEmojiManager::createLoaderWithSetId(
  678. not_null<DocumentData*> document,
  679. SizeTag tag,
  680. int sizeOverride
  681. ) -> LoaderWithSetId {
  682. if (const auto sticker = document->sticker()) {
  683. return {
  684. std::make_unique<CustomEmojiLoader>(document, tag, sizeOverride),
  685. sticker->set.id,
  686. document->emojiUsesTextColor(),
  687. };
  688. }
  689. return createLoaderWithSetId(document->id, tag, sizeOverride);
  690. }
  691. auto CustomEmojiManager::createLoaderWithSetId(
  692. DocumentId documentId,
  693. SizeTag tag,
  694. int sizeOverride
  695. ) -> LoaderWithSetId {
  696. auto result = std::make_unique<CustomEmojiLoader>(
  697. _owner,
  698. documentId,
  699. tag,
  700. sizeOverride);
  701. if (const auto document = result->document()) {
  702. if (const auto sticker = document->sticker()) {
  703. return {
  704. std::move(result),
  705. sticker->set.id,
  706. document->emojiUsesTextColor(),
  707. };
  708. }
  709. } else {
  710. const auto i = SizeIndex(tag);
  711. _loaders[i][documentId].push_back(base::make_weak(result));
  712. _pendingForRequest.emplace(documentId);
  713. if (!_requestId && _pendingForRequest.size() == 1) {
  714. crl::on_main(this, [=] { request(); });
  715. }
  716. }
  717. return { std::move(result), uint64(), false };
  718. }
  719. QString CustomEmojiManager::lookupSetName(uint64 setId) {
  720. const auto &sets = _owner->stickers().sets();
  721. const auto i = sets.find(setId);
  722. return (i != end(sets)) ? i->second->title : QString();
  723. }
  724. void CustomEmojiManager::request() {
  725. auto ids = QVector<MTPlong>();
  726. ids.reserve(std::min(kMaxPerRequest, int(_pendingForRequest.size())));
  727. while (!_pendingForRequest.empty() && ids.size() < kMaxPerRequest) {
  728. const auto i = _pendingForRequest.end() - 1;
  729. ids.push_back(MTP_long(*i));
  730. _pendingForRequest.erase(i);
  731. }
  732. if (ids.isEmpty()) {
  733. return;
  734. }
  735. const auto api = &_owner->session().api();
  736. _requestId = api->request(MTPmessages_GetCustomEmojiDocuments(
  737. MTP_vector<MTPlong>(ids)
  738. )).done([=](const MTPVector<MTPDocument> &result) {
  739. for (const auto &entry : result.v) {
  740. const auto document = _owner->processDocument(entry);
  741. fillColoredFlags(document);
  742. processLoaders(document);
  743. processListeners(document);
  744. requestSetFor(document);
  745. }
  746. requestFinished();
  747. }).fail([=] {
  748. LOG(("API Error: Failed to get documents for emoji."));
  749. for (const auto &id : ids) {
  750. processListeners(_owner->document(id.v));
  751. }
  752. requestFinished();
  753. }).send();
  754. }
  755. void CustomEmojiManager::fillColoredFlags(not_null<DocumentData*> document) {
  756. if (document->emojiUsesTextColor()) {
  757. const auto id = document->id;
  758. for (auto &instances : _instances) {
  759. const auto i = instances.find(id);
  760. if (i != end(instances)) {
  761. i->second->setColored();
  762. }
  763. }
  764. }
  765. }
  766. void CustomEmojiManager::processLoaders(not_null<DocumentData*> document) {
  767. const auto id = document->id;
  768. for (auto &loaders : _loaders) {
  769. if (const auto list = loaders.take(id)) {
  770. for (const auto &weak : *list) {
  771. if (const auto strong = weak.get()) {
  772. strong->resolved(document);
  773. }
  774. }
  775. }
  776. }
  777. }
  778. void CustomEmojiManager::processListeners(
  779. not_null<DocumentData*> document) {
  780. const auto id = document->id;
  781. if (const auto listeners = _resolvers.take(id)) {
  782. for (const auto &listener : *listeners) {
  783. const auto i = _listeners.find(listener);
  784. if (i != end(_listeners) && i->second.remove(id)) {
  785. if (i->second.empty()) {
  786. _listeners.erase(i);
  787. }
  788. listener->customEmojiResolveDone(document);
  789. }
  790. }
  791. }
  792. }
  793. void CustomEmojiManager::requestSetFor(not_null<DocumentData*> document) {
  794. const auto sticker = document->sticker();
  795. if (!sticker || !sticker->set.id) {
  796. return;
  797. }
  798. const auto &sets = document->owner().stickers().sets();
  799. const auto i = sets.find(sticker->set.id);
  800. if (i != end(sets)) {
  801. return;
  802. }
  803. const auto session = &document->session();
  804. session->api().scheduleStickerSetRequest(
  805. sticker->set.id,
  806. sticker->set.accessHash);
  807. if (_requestSetsScheduled) {
  808. return;
  809. }
  810. _requestSetsScheduled = true;
  811. crl::on_main(this, [=] {
  812. _requestSetsScheduled = false;
  813. session->api().requestStickerSets();
  814. });
  815. }
  816. int CustomEmojiManager::SizeIndex(SizeTag tag) {
  817. const auto result = static_cast<int>(tag);
  818. Ensures(result >= 0 && result < kSizeCount);
  819. return result;
  820. }
  821. void CustomEmojiManager::requestFinished() {
  822. _requestId = 0;
  823. if (!_pendingForRequest.empty()) {
  824. request();
  825. }
  826. }
  827. void CustomEmojiManager::repaintLater(
  828. not_null<Ui::CustomEmoji::Instance*> instance,
  829. Ui::CustomEmoji::RepaintRequest request) {
  830. auto &bunch = _repaints[request.duration];
  831. if (bunch.when < request.when) {
  832. if (bunch.when > 0) {
  833. for (const auto &already : bunch.instances) {
  834. if (already.get() == instance) {
  835. // Still waiting for full bunch repaint, don't bump.
  836. return;
  837. }
  838. }
  839. }
  840. bunch.when = request.when;
  841. #if 0 // inject-to-on_main
  842. _repaintsLastAdded = request.when;
  843. #endif
  844. }
  845. bunch.instances.emplace_back(instance);
  846. scheduleRepaintTimer();
  847. }
  848. bool CustomEmojiManager::checkEmptyRepaints() {
  849. if (!_repaints.empty()) {
  850. return false;
  851. #if 0 // inject-to-on_main
  852. } else if (_repaintsLifetime
  853. && crl::now() >= _repaintsLastAdded + kUnsubscribeUpdatesDelay) {
  854. _repaintsLifetime.destroy();
  855. #endif
  856. }
  857. return true;
  858. }
  859. void CustomEmojiManager::scheduleRepaintTimer() {
  860. if (checkEmptyRepaints() || _repaintTimerScheduled) {
  861. return;
  862. }
  863. #if 0 // inject-to-on_main
  864. if (!_repaintsLifetime) {
  865. crl::on_main_update_requests(
  866. ) | rpl::start_with_next([=] {
  867. invokeRepaints();
  868. }, _repaintsLifetime);
  869. }
  870. #endif
  871. _repaintTimerScheduled = true;
  872. Ui::PostponeCall(this, [=] {
  873. _repaintTimerScheduled = false;
  874. auto next = crl::time();
  875. for (const auto &[duration, bunch] : _repaints) {
  876. if (!next || next > bunch.when) {
  877. next = bunch.when;
  878. }
  879. }
  880. if (next && (!_repaintNext || _repaintNext > next)) {
  881. const auto now = crl::now();
  882. if (now >= next) {
  883. _repaintNext = 0;
  884. _repaintTimer.cancel();
  885. invokeRepaints();
  886. } else {
  887. _repaintNext = next;
  888. _repaintTimer.callOnce(next - now);
  889. }
  890. }
  891. });
  892. }
  893. void CustomEmojiManager::invokeRepaints() {
  894. _repaintNext = 0;
  895. if (checkEmptyRepaints()) {
  896. return;
  897. }
  898. const auto now = crl::now();
  899. auto repaint = std::vector<base::weak_ptr<Ui::CustomEmoji::Instance>>();
  900. for (auto i = begin(_repaints); i != end(_repaints);) {
  901. if (i->second.when > now) {
  902. ++i;
  903. continue;
  904. }
  905. auto &list = i->second.instances;
  906. if (repaint.empty()) {
  907. repaint = std::move(list);
  908. } else {
  909. repaint.insert(
  910. end(repaint),
  911. std::make_move_iterator(begin(list)),
  912. std::make_move_iterator(end(list)));
  913. }
  914. i = _repaints.erase(i);
  915. }
  916. if (!repaint.empty()) {
  917. for (const auto &weak : repaint) {
  918. if (const auto strong = weak.get()) {
  919. strong->repaint();
  920. }
  921. }
  922. } else if (_repaintTimer.isActive()) {
  923. return;
  924. }
  925. scheduleRepaintTimer();
  926. }
  927. Main::Session &CustomEmojiManager::session() const {
  928. return _owner->session();
  929. }
  930. Session &CustomEmojiManager::owner() const {
  931. return *_owner;
  932. }
  933. uint64 CustomEmojiManager::coloredSetId() const {
  934. return _coloredSetId;
  935. }
  936. TextWithEntities CustomEmojiManager::creditsEmoji(QMargins padding) {
  937. return Ui::Text::SingleCustomEmoji(
  938. registerInternalEmoji(
  939. Ui::GenerateStars(st::normalFont->height, 1),
  940. padding,
  941. false));
  942. }
  943. TextWithEntities CustomEmojiManager::ministarEmoji(QMargins padding) {
  944. return Ui::Text::SingleCustomEmoji(
  945. registerInternalEmoji(
  946. Ui::GenerateStars(st::giftBoxByStarsStyle.font->height, 1),
  947. padding,
  948. false));
  949. }
  950. QString CustomEmojiManager::registerInternalEmoji(
  951. QImage emoji,
  952. QMargins padding,
  953. bool textColor) {
  954. _internalEmoji.push_back({ std::move(emoji), textColor });
  955. return InternalPrefix()
  956. + QString::number(_internalEmoji.size() - 1)
  957. + InternalPadding(padding);
  958. }
  959. QString CustomEmojiManager::registerInternalEmoji(
  960. const style::icon &icon,
  961. QMargins padding,
  962. bool textColor) {
  963. const auto i = _iconEmoji.find(&icon);
  964. if (i != end(_iconEmoji)) {
  965. return i->second + InternalPadding(padding);
  966. }
  967. auto image = QImage(
  968. icon.size() * style::DevicePixelRatio(),
  969. QImage::Format_ARGB32_Premultiplied);
  970. image.fill(Qt::transparent);
  971. image.setDevicePixelRatio(style::DevicePixelRatio());
  972. auto p = QPainter(&image);
  973. icon.paint(p, 0, 0, icon.width());
  974. p.end();
  975. const auto result = registerInternalEmoji(
  976. std::move(image),
  977. QMargins{},
  978. textColor);
  979. _iconEmoji.emplace(&icon, result);
  980. return result + InternalPadding(padding);
  981. }
  982. [[nodiscard]] QString CustomEmojiManager::peerUserpicEmojiData(
  983. not_null<PeerData*> peer,
  984. QMargins padding,
  985. bool respectSavedRepliesEtc) {
  986. const auto id = !respectSavedRepliesEtc
  987. ? QString::number(peer->id.value)
  988. : peer->isSelf()
  989. ? u"self"_q
  990. : peer->isRepliesChat()
  991. ? u"replies"_q
  992. : QString::number(peer->id.value);
  993. return UserpicEmojiPrefix() + id + InternalPadding(padding);
  994. }
  995. int FrameSizeFromTag(SizeTag tag) {
  996. const auto emoji = EmojiSizeFromTag(tag);
  997. const auto factor = style::DevicePixelRatio();
  998. return Ui::Text::AdjustCustomEmojiSize(emoji / factor) * factor;
  999. }
  1000. QString SerializeCustomEmojiId(DocumentId id) {
  1001. return QString::number(id);
  1002. }
  1003. QString SerializeCustomEmojiId(not_null<DocumentData*> document) {
  1004. return SerializeCustomEmojiId(document->id);
  1005. }
  1006. DocumentId ParseCustomEmojiData(QStringView data) {
  1007. return data.toULongLong();
  1008. }
  1009. TextWithEntities SingleCustomEmoji(DocumentId id) {
  1010. return Ui::Text::SingleCustomEmoji(SerializeCustomEmojiId(id));
  1011. }
  1012. TextWithEntities SingleCustomEmoji(not_null<DocumentData*> document) {
  1013. return SingleCustomEmoji(document->id);
  1014. }
  1015. bool AllowEmojiWithoutPremium(
  1016. not_null<PeerData*> peer,
  1017. DocumentData *exactEmoji) {
  1018. if (peer->isSelf()) {
  1019. return true;
  1020. } else if (!exactEmoji) {
  1021. return false;
  1022. } else if (const auto sticker = exactEmoji->sticker()) {
  1023. if (const auto channel = peer->asMegagroup()) {
  1024. if (channel->mgInfo->emojiSet.id == sticker->set.id) {
  1025. return (sticker->set.id != 0);
  1026. }
  1027. }
  1028. }
  1029. return false;
  1030. }
  1031. void InsertCustomEmoji(
  1032. not_null<Ui::InputField*> field,
  1033. not_null<DocumentData*> document) {
  1034. const auto sticker = document->sticker();
  1035. if (!sticker || sticker->alt.isEmpty()) {
  1036. return;
  1037. }
  1038. Ui::InsertCustomEmojiAtCursor(
  1039. field,
  1040. field->textCursor(),
  1041. sticker->alt,
  1042. Ui::InputField::CustomEmojiLink(SerializeCustomEmojiId(document)));
  1043. }
  1044. Ui::Text::CustomEmojiFactory ReactedMenuFactory(
  1045. not_null<Main::Session*> session) {
  1046. return [owner = &session->data()](
  1047. QStringView data,
  1048. const Ui::Text::MarkedContext &context
  1049. ) -> std::unique_ptr<Ui::Text::CustomEmoji> {
  1050. const auto prefix = u"default:"_q;
  1051. if (data.startsWith(prefix)) {
  1052. const auto &list = owner->reactions().list(
  1053. Data::Reactions::Type::All);
  1054. const auto emoji = data.mid(prefix.size()).toString();
  1055. const auto id = Data::ReactionId{ { emoji } };
  1056. const auto i = ranges::find(list, id, &Data::Reaction::id);
  1057. if (i != end(list)) {
  1058. const auto document = i->centerIcon
  1059. ? not_null(i->centerIcon)
  1060. : i->selectAnimation;
  1061. const auto size = st::emojiSize * (i->centerIcon ? 2 : 1);
  1062. const auto tag = Data::CustomEmojiManager::SizeTag::Normal;
  1063. const auto ratio = style::DevicePixelRatio();
  1064. const auto skip = (Data::FrameSizeFromTag(tag) / ratio - size) / 2;
  1065. return std::make_unique<Ui::Text::FirstFrameEmoji>(
  1066. std::make_unique<Ui::Text::ShiftedEmoji>(
  1067. owner->customEmojiManager().create(
  1068. document,
  1069. context.repaint,
  1070. tag,
  1071. size),
  1072. QPoint(skip, skip)));
  1073. }
  1074. }
  1075. return owner->customEmojiManager().create(data, context.repaint);
  1076. };
  1077. }
  1078. QString CollectibleCustomEmojiId(Data::EmojiStatusCollectible &data) {
  1079. return CollectiblePrefix() + QString::number(data.id);
  1080. }
  1081. QString EmojiStatusCustomId(const EmojiStatusId &id) {
  1082. return id.collectible
  1083. ? CollectibleCustomEmojiId(*id.collectible)
  1084. : SerializeCustomEmojiId(id.documentId);
  1085. }
  1086. } // namespace Data