stickers_list_footer.cpp 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554
  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 "chat_helpers/stickers_list_footer.h"
  8. #include "chat_helpers/emoji_keywords.h"
  9. #include "chat_helpers/stickers_emoji_pack.h"
  10. #include "chat_helpers/stickers_lottie.h"
  11. #include "core/application.h"
  12. #include "data/stickers/data_stickers_set.h"
  13. #include "data/stickers/data_stickers.h"
  14. #include "data/stickers/data_custom_emoji.h"
  15. #include "data/data_file_origin.h"
  16. #include "data/data_channel.h"
  17. #include "data/data_session.h"
  18. #include "data/data_document.h"
  19. #include "data/data_document_media.h"
  20. #include "main/main_app_config.h"
  21. #include "main/main_session.h"
  22. #include "lang/lang_keys.h"
  23. #include "lottie/lottie_single_player.h"
  24. #include "ui/dpr/dpr_icon.h"
  25. #include "ui/dpr/dpr_image.h"
  26. #include "ui/widgets/fields/input_field.h"
  27. #include "ui/widgets/buttons.h"
  28. #include "ui/painter.h"
  29. #include "ui/rect_part.h"
  30. #include "styles/style_chat_helpers.h"
  31. #include <QtWidgets/QApplication>
  32. namespace ChatHelpers {
  33. namespace {
  34. constexpr auto kEmojiSectionSetIdBase = uint64(0x77FF'FFFF'FFFF'FFF0ULL);
  35. constexpr auto kEmojiSearchLimit = 32;
  36. using EmojiSection = Ui::Emoji::Section;
  37. void UpdateAnimated(anim::value &value, int to) {
  38. if (int(base::SafeRound(value.to())) == to) {
  39. return;
  40. }
  41. value = anim::value(
  42. (value.from() != value.to()) ? value.from() : to,
  43. to);
  44. }
  45. void UpdateAnimated(
  46. anim::value &value,
  47. int to,
  48. ValidateIconAnimations animations) {
  49. if (animations == ValidateIconAnimations::Full) {
  50. value.start(to);
  51. } else {
  52. value = anim::value(to, to);
  53. }
  54. }
  55. } // namespace
  56. uint64 EmojiSectionSetId(EmojiSection section) {
  57. Expects(section >= EmojiSection::Recent
  58. && section <= EmojiSection::Symbols);
  59. return kEmojiSectionSetIdBase + static_cast<uint64>(section) + 1;
  60. }
  61. uint64 RecentEmojiSectionSetId() {
  62. return EmojiSectionSetId(EmojiSection::Recent);
  63. }
  64. uint64 AllEmojiSectionSetId() {
  65. return kEmojiSectionSetIdBase;
  66. }
  67. uint64 SearchEmojiSectionSetId() {
  68. return kEmojiSectionSetIdBase
  69. + static_cast<uint64>(EmojiSection::Symbols)
  70. + 2;
  71. }
  72. std::optional<EmojiSection> SetIdEmojiSection(uint64 id) {
  73. const auto base = RecentEmojiSectionSetId();
  74. if (id < base) {
  75. return {};
  76. }
  77. const auto index = id - base;
  78. return (index <= uint64(EmojiSection::Symbols))
  79. ? static_cast<EmojiSection>(index)
  80. : std::optional<EmojiSection>();
  81. }
  82. [[nodiscard]] std::vector<QString> GifSearchEmojiFallback() {
  83. return {
  84. u"\xf0\x9f\x91\x8d"_q,
  85. u"\xf0\x9f\x98\x98"_q,
  86. u"\xf0\x9f\x98\x8d"_q,
  87. u"\xf0\x9f\x98\xa1"_q,
  88. u"\xf0\x9f\xa5\xb3"_q,
  89. u"\xf0\x9f\x98\x82"_q,
  90. u"\xf0\x9f\x98\xae"_q,
  91. u"\xf0\x9f\x99\x84"_q,
  92. u"\xf0\x9f\x98\x8e"_q,
  93. u"\xf0\x9f\x91\x8e"_q,
  94. };
  95. }
  96. rpl::producer<std::vector<GifSection>> GifSectionsValue(
  97. not_null<Main::Session*> session) {
  98. const auto config = &session->appConfig();
  99. return config->value(
  100. ) | rpl::map([=] {
  101. return config->get<std::vector<QString>>(
  102. u"gif_search_emojies"_q,
  103. GifSearchEmojiFallback());
  104. }) | rpl::distinct_until_changed(
  105. ) | rpl::map([=](const std::vector<QString> &emoji) {
  106. const auto list = ranges::views::all(
  107. emoji
  108. ) | ranges::views::transform([](const QString &val) {
  109. return Ui::Emoji::Find(val);
  110. }) | ranges::views::filter([](EmojiPtr emoji) {
  111. return emoji != nullptr;
  112. }) | ranges::to_vector;
  113. const auto pack = &session->emojiStickersPack();
  114. return rpl::single(
  115. rpl::empty_value()
  116. ) | rpl::then(
  117. pack->refreshed()
  118. ) | rpl::map([=, list = std::move(list)] {
  119. return list | ranges::views::transform([&](EmojiPtr emoji) {
  120. const auto document = pack->stickerForEmoji(emoji).document;
  121. return GifSection{ document, emoji };
  122. }) | ranges::views::filter([](GifSection section) {
  123. return (section.document != nullptr);
  124. }) | ranges::to_vector;
  125. }) | rpl::distinct_until_changed();
  126. }) | rpl::flatten_latest();
  127. }
  128. [[nodiscard]] std::vector<EmojiPtr> SearchEmoji(
  129. const std::vector<QString> &query,
  130. base::flat_set<EmojiPtr> &outResultSet) {
  131. auto result = std::vector<EmojiPtr>();
  132. const auto pushPlain = [&](EmojiPtr emoji) {
  133. if (result.size() < kEmojiSearchLimit
  134. && outResultSet.emplace(emoji).second) {
  135. result.push_back(emoji);
  136. }
  137. if (const auto original = emoji->original(); original != emoji) {
  138. outResultSet.emplace(original);
  139. }
  140. };
  141. auto refreshed = false;
  142. auto &keywords = Core::App().emojiKeywords();
  143. for (const auto &entry : query) {
  144. if (const auto emoji = Ui::Emoji::Find(entry)) {
  145. pushPlain(emoji);
  146. if (result.size() >= kEmojiSearchLimit) {
  147. return result;
  148. }
  149. } else if (!entry.isEmpty()) {
  150. if (!refreshed) {
  151. refreshed = true;
  152. keywords.refresh();
  153. }
  154. const auto list = keywords.queryMine(entry);
  155. for (const auto &entry : list) {
  156. pushPlain(entry.emoji);
  157. if (result.size() >= kEmojiSearchLimit) {
  158. return result;
  159. }
  160. }
  161. }
  162. }
  163. return result;
  164. }
  165. StickerIcon::StickerIcon(uint64 setId) : setId(setId) {
  166. }
  167. StickerIcon::StickerIcon(
  168. not_null<Data::StickersSet*> set,
  169. DocumentData *sticker,
  170. int pixw,
  171. int pixh)
  172. : setId(set->id)
  173. , set(set)
  174. , sticker(sticker)
  175. , pixw(std::max(pixw, 1))
  176. , pixh(std::max(pixh, 1)) {
  177. }
  178. StickerIcon::StickerIcon(StickerIcon&&) = default;
  179. StickerIcon &StickerIcon::operator=(StickerIcon&&) = default;
  180. StickerIcon::~StickerIcon() = default;
  181. void StickerIcon::ensureMediaCreated() const {
  182. if (!sticker) {
  183. return;
  184. } else if (set->hasThumbnail()) {
  185. if (!thumbnailMedia) {
  186. thumbnailMedia = set->createThumbnailView();
  187. set->loadThumbnail();
  188. }
  189. } else if (!stickerMedia) {
  190. stickerMedia = sticker->createMediaView();
  191. stickerMedia->thumbnailWanted(sticker->stickerSetOrigin());
  192. }
  193. }
  194. template <typename UpdateCallback>
  195. StickersListFooter::ScrollState::ScrollState(UpdateCallback &&callback)
  196. : animation([=](crl::time now) {
  197. callback();
  198. return animationCallback(now);
  199. }) {
  200. }
  201. bool StickersListFooter::ScrollState::animationCallback(crl::time now) {
  202. if (anim::Disabled()) {
  203. now += st::stickerIconMove;
  204. }
  205. if (!animationStart) {
  206. return false;
  207. }
  208. const auto dt = (now - animationStart) / float64(st::stickerIconMove);
  209. if (dt >= 1.) {
  210. animationStart = 0;
  211. x.finish();
  212. selectionX.finish();
  213. selectionWidth.finish();
  214. return false;
  215. }
  216. x.update(dt, anim::linear);
  217. selectionX.update(dt, anim::easeOutCubic);
  218. selectionWidth.update(dt, anim::easeOutCubic);
  219. return true;
  220. }
  221. GradientPremiumStar::GradientPremiumStar() {
  222. style::PaletteChanged(
  223. ) | rpl::start_with_next([=] {
  224. _image = QImage();
  225. }, _lifetime);
  226. }
  227. QImage GradientPremiumStar::image() const {
  228. if (_image.isNull()) {
  229. renderOnDemand();
  230. }
  231. return _image;
  232. }
  233. void GradientPremiumStar::renderOnDemand() const {
  234. const auto size = st::emojiStatusDefault.size();
  235. const auto mask = st::emojiStatusDefault.instance(Qt::white);
  236. const auto factor = style::DevicePixelRatio();
  237. _image = QImage(
  238. size * factor,
  239. QImage::Format_ARGB32_Premultiplied);
  240. _image.setDevicePixelRatio(factor);
  241. QPainter p(&_image);
  242. auto gradient = QLinearGradient(
  243. QPoint(0, size.height()),
  244. QPoint(size.width(), 0));
  245. gradient.setStops({
  246. { 0., st::stickerPanPremium1->c },
  247. { 1., st::stickerPanPremium2->c },
  248. });
  249. p.fillRect(QRect(QPoint(), size), gradient);
  250. p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
  251. p.drawImage(QRect(QPoint(), size), mask);
  252. }
  253. StickersListFooter::StickersListFooter(Descriptor &&descriptor)
  254. : InnerFooter(
  255. descriptor.parent,
  256. descriptor.st ? *descriptor.st : st::defaultEmojiPan)
  257. , _session(descriptor.session)
  258. , _customTextColor(std::move(descriptor.customTextColor))
  259. , _paused(std::move(descriptor.paused))
  260. , _features(descriptor.features)
  261. , _iconState([=] { update(); })
  262. , _subiconState([=] { update(); })
  263. , _selectionBg(st::emojiPanRadius, st().categoriesBgOver)
  264. , _subselectionBg(st().iconArea / 2, st().categoriesBgOver)
  265. , _forceFirstFrame(descriptor.forceFirstFrame) {
  266. setMouseTracking(true);
  267. _iconsLeft = st().iconSkip
  268. + (_features.stickersSettings ? st().iconWidth : 0);
  269. _iconsRight = st().iconSkip;
  270. _session->downloaderTaskFinished(
  271. ) | rpl::start_with_next([=] {
  272. update();
  273. }, lifetime());
  274. }
  275. void StickersListFooter::clearHeavyData() {
  276. enumerateIcons([&](const IconInfo &info) {
  277. auto &icon = _icons[info.index];
  278. icon.webm = nullptr;
  279. icon.lottie = nullptr;
  280. icon.lifetime.destroy();
  281. icon.stickerMedia = nullptr;
  282. if (!info.visible) {
  283. icon.savedFrame = QImage();
  284. }
  285. return true;
  286. });
  287. }
  288. void StickersListFooter::paintExpanding(
  289. Painter &p,
  290. QRect clip,
  291. float64 radius,
  292. RectPart origin) {
  293. const auto delta = ((origin | RectPart::None) & RectPart::FullBottom)
  294. ? (height() - clip.height())
  295. : 0;
  296. const auto shift = QPoint(clip.x(), clip.y() - delta);
  297. p.translate(shift);
  298. const auto context = ExpandingContext{
  299. .clip = clip.translated(-shift),
  300. .progress = clip.height() / float64(height()),
  301. .radius = int(std::ceil(radius)),
  302. .expanding = true,
  303. };
  304. paint(p, context);
  305. p.translate(-shift);
  306. p.setClipping(false);
  307. }
  308. int StickersListFooter::IconFrameSize() {
  309. return Data::FrameSizeFromTag(
  310. Data::CustomEmojiManager::SizeTag::SetIcon
  311. ) / style::DevicePixelRatio();
  312. }
  313. void StickersListFooter::enumerateVisibleIcons(
  314. Fn<void(const IconInfo &)> callback) const {
  315. enumerateIcons([&](const IconInfo &info) {
  316. if (info.visible) {
  317. callback(info);
  318. } else if (info.adjustedLeft > 0) {
  319. return false;
  320. }
  321. return true;
  322. });
  323. }
  324. void StickersListFooter::enumerateIcons(
  325. Fn<bool(const IconInfo &)> callback) const {
  326. auto left = 0;
  327. const auto iconsX = int(base::SafeRound(_iconState.x.current()));
  328. const auto shift = _iconsLeft - iconsX;
  329. const auto emojiId = AllEmojiSectionSetId();
  330. const auto right = width();
  331. for (auto i = 0, count = int(_icons.size()); i != count; ++i) {
  332. auto &icon = _icons[i];
  333. const auto width = (icon.setId == emojiId)
  334. ? _subiconsWidthAnimation.value(_subiconsExpanded
  335. ? _subiconsWidth
  336. : _singleWidth)
  337. : _singleWidth;
  338. const auto shifted = shift + left;
  339. const auto visible = (shifted + width > 0 && shifted < right);
  340. const auto result = callback({
  341. .index = i,
  342. .left = left,
  343. .adjustedLeft = shifted,
  344. .width = int(base::SafeRound(width)),
  345. .visible = visible,
  346. });
  347. if (!result) {
  348. break;
  349. }
  350. left += width;
  351. }
  352. }
  353. void StickersListFooter::enumerateSubicons(
  354. Fn<bool(const IconInfo &)> callback) const {
  355. auto left = 0;
  356. const auto iconsX = int(base::SafeRound(_subiconState.x.current()));
  357. const auto shift = -iconsX;
  358. const auto right = _subiconsWidth;
  359. using Section = Ui::Emoji::Section;
  360. for (auto i = int(Section::People); i <= int(Section::Symbols); ++i) {
  361. const auto shifted = shift + left;
  362. const auto visible = (shifted + _singleWidth > 0 && shifted < right);
  363. const auto result = callback({
  364. .index = i - int(Section::People),
  365. .left = left,
  366. .adjustedLeft = shifted,
  367. .width = _singleWidth,
  368. .visible = visible,
  369. });
  370. if (!result) {
  371. break;
  372. }
  373. left += _singleWidth;
  374. }
  375. }
  376. auto StickersListFooter::iconInfo(int index) const -> IconInfo {
  377. if (index < 0) {
  378. const auto iconsX = int(base::SafeRound(_iconState.x.current()));
  379. return {
  380. .index = -1,
  381. .left = -_singleWidth - _iconsLeft,
  382. .adjustedLeft = -_singleWidth - _iconsLeft - iconsX,
  383. .width = _singleWidth,
  384. .visible = false,
  385. };
  386. }
  387. auto result = IconInfo();
  388. enumerateIcons([&](const IconInfo &info) {
  389. if (info.index == index) {
  390. result = info;
  391. return false;
  392. }
  393. return true;
  394. });
  395. return result;
  396. }
  397. auto StickersListFooter::subiconInfo(int index) const -> IconInfo {
  398. auto result = IconInfo();
  399. enumerateSubicons([&](const IconInfo &info) {
  400. if (info.index == index) {
  401. result = info;
  402. return false;
  403. }
  404. return true;
  405. });
  406. return result;
  407. }
  408. void StickersListFooter::preloadImages() {
  409. enumerateVisibleIcons([&](const IconInfo &info) {
  410. const auto &icon = _icons[info.index];
  411. if (const auto sticker = icon.sticker) {
  412. Assert(icon.set != nullptr);
  413. if (icon.set->hasThumbnail()) {
  414. icon.set->loadThumbnail();
  415. } else {
  416. sticker->loadThumbnail(sticker->stickerSetOrigin());
  417. }
  418. }
  419. });
  420. }
  421. void StickersListFooter::validateSelectedIcon(
  422. uint64 setId,
  423. ValidateIconAnimations animations) {
  424. _activeByScrollId = setId;
  425. using EmojiSection = Ui::Emoji::Section;
  426. auto favedIconIndex = -1;
  427. auto newSelected = -1;
  428. auto newSubSelected = -1;
  429. const auto emojiSection = SetIdEmojiSection(setId);
  430. const auto isEmojiSection = emojiSection.has_value()
  431. && (emojiSection != EmojiSection::Recent);
  432. const auto allEmojiSetId = AllEmojiSectionSetId();
  433. for (auto i = 0, l = int(_icons.size()); i != l; ++i) {
  434. if (_icons[i].setId == setId
  435. || (_icons[i].setId == Data::Stickers::FavedSetId
  436. && setId == Data::Stickers::RecentSetId)) {
  437. newSelected = i;
  438. break;
  439. } else if (_icons[i].setId == Data::Stickers::FavedSetId
  440. && setId != SearchEmojiSectionSetId()) {
  441. favedIconIndex = i;
  442. } else if (isEmojiSection && _icons[i].setId == allEmojiSetId) {
  443. newSelected = i;
  444. newSubSelected = setId - EmojiSectionSetId(EmojiSection::People);
  445. }
  446. }
  447. setSelectedIcon(
  448. (newSelected >= 0
  449. ? newSelected
  450. : (favedIconIndex >= 0)
  451. ? favedIconIndex
  452. : -1),
  453. animations);
  454. setSelectedSubicon(
  455. (newSubSelected >= 0 ? newSubSelected : 0),
  456. animations);
  457. }
  458. void StickersListFooter::updateEmojiSectionWidth() {
  459. const auto expanded = (_iconState.selected >= 0)
  460. && (_iconState.selected < _icons.size())
  461. && (_icons[_iconState.selected].setId == AllEmojiSectionSetId());
  462. if (_subiconsExpanded == expanded) {
  463. return;
  464. }
  465. _subiconsExpanded = expanded;
  466. _subiconsWidthAnimation.start(
  467. [=] { updateEmojiWidthCallback(); },
  468. expanded ? _singleWidth : _subiconsWidth,
  469. expanded ? _subiconsWidth : _singleWidth,
  470. st::slideDuration);
  471. }
  472. void StickersListFooter::updateEmojiWidthCallback() {
  473. refreshScrollableDimensions();
  474. const auto info = iconInfo(_iconState.selected);
  475. UpdateAnimated(_iconState.selectionX, info.left);
  476. UpdateAnimated(_iconState.selectionWidth, info.width);
  477. if (_iconState.animation.animating()) {
  478. _iconState.animationCallback(crl::now());
  479. }
  480. update();
  481. }
  482. void StickersListFooter::setSelectedIcon(
  483. int newSelected,
  484. ValidateIconAnimations animations) {
  485. if (_iconState.selected == newSelected) {
  486. return;
  487. }
  488. if ((_iconState.selected < 0) != (newSelected < 0)) {
  489. animations = ValidateIconAnimations::None;
  490. }
  491. _iconState.selected = newSelected;
  492. updateEmojiSectionWidth();
  493. const auto info = iconInfo(_iconState.selected);
  494. UpdateAnimated(_iconState.selectionX, info.left, animations);
  495. UpdateAnimated(_iconState.selectionWidth, info.width, animations);
  496. const auto relativeLeft = info.left - _iconsLeft;
  497. const auto iconsWidthForCentering = 2 * relativeLeft + info.width;
  498. const auto iconsXFinal = std::clamp(
  499. (_iconsLeft + iconsWidthForCentering + _iconsRight - width()) / 2,
  500. 0,
  501. _iconState.max);
  502. if (animations == ValidateIconAnimations::None) {
  503. _iconState.x = anim::value(iconsXFinal, iconsXFinal);
  504. _iconState.animation.stop();
  505. } else {
  506. _iconState.x.start(iconsXFinal);
  507. _iconState.animationStart = crl::now();
  508. _iconState.animation.start();
  509. }
  510. updateSelected();
  511. update();
  512. }
  513. void StickersListFooter::setSelectedSubicon(
  514. int newSelected,
  515. ValidateIconAnimations animations) {
  516. if (_subiconState.selected == newSelected) {
  517. return;
  518. }
  519. _subiconState.selected = newSelected;
  520. const auto info = subiconInfo(_subiconState.selected);
  521. const auto relativeLeft = info.left;
  522. const auto subiconsWidthForCentering = 2 * relativeLeft + info.width;
  523. const auto subiconsXFinal = std::clamp(
  524. (subiconsWidthForCentering - _subiconsWidth) / 2,
  525. 0,
  526. _subiconState.max);
  527. if (animations == ValidateIconAnimations::None) {
  528. _subiconState.x = anim::value(subiconsXFinal, subiconsXFinal);
  529. _subiconState.animation.stop();
  530. } else {
  531. _subiconState.x.start(subiconsXFinal);
  532. _subiconState.animationStart = crl::now();
  533. _subiconState.animation.start();
  534. }
  535. updateSelected();
  536. update();
  537. }
  538. void StickersListFooter::processHideFinished() {
  539. _selected = _pressed = SpecialOver::None;
  540. _iconState.animation.stop();
  541. _iconState.animationStart = 0;
  542. _iconState.x.finish();
  543. _iconState.selectionX.finish();
  544. _iconState.selectionWidth.finish();
  545. _subiconState.animation.stop();
  546. _subiconState.animationStart = 0;
  547. _subiconState.x.finish();
  548. }
  549. void StickersListFooter::leaveToChildEvent(QEvent *e, QWidget *child) {
  550. _iconsMousePos = QCursor::pos();
  551. updateSelected();
  552. }
  553. void StickersListFooter::paintEvent(QPaintEvent *e) {
  554. auto p = Painter(this);
  555. _repaintScheduled = false;
  556. paint(p, {});
  557. }
  558. void StickersListFooter::paint(
  559. Painter &p,
  560. const ExpandingContext &context) const {
  561. if (_icons.empty()) {
  562. return;
  563. }
  564. if (_features.stickersSettings) {
  565. paintStickerSettingsIcon(p);
  566. }
  567. auto clip = QRect(
  568. _iconsLeft,
  569. _iconsTop,
  570. width() - _iconsLeft - _iconsRight,
  571. st().footer);
  572. if (rtl()) {
  573. clip.moveLeft(width() - _iconsLeft - clip.width());
  574. }
  575. if (context.expanding) {
  576. const auto both = clip.intersected(
  577. context.clip.marginsRemoved(
  578. { 0/*context.radius*/, 0, context.radius, 0 }));
  579. if (both.isEmpty()) {
  580. return;
  581. }
  582. p.setClipRect(both);
  583. } else {
  584. p.setClipRect(clip);
  585. }
  586. paintSelectionBg(p, context);
  587. const auto iconCacheSize = QSize(_singleWidth, st().footer);
  588. const auto full = iconCacheSize * style::DevicePixelRatio();
  589. if (_setIconCache.size() != full) {
  590. _setIconCache = QImage(full, QImage::Format_ARGB32_Premultiplied);
  591. _setIconCache.setDevicePixelRatio(style::DevicePixelRatio());
  592. }
  593. const auto now = crl::now();
  594. const auto paused = _paused();
  595. p.setPen(st::windowFg);
  596. enumerateVisibleIcons([&](const IconInfo &info) {
  597. paintSetIcon(p, context, info, now, paused);
  598. });
  599. paintLeftRightFading(p, context);
  600. }
  601. void StickersListFooter::paintSelectionBg(
  602. QPainter &p,
  603. const ExpandingContext &context) const {
  604. auto selxrel = _iconsLeft + qRound(_iconState.selectionX.current());
  605. auto selx = selxrel - qRound(_iconState.x.current());
  606. const auto selw = qRound(_iconState.selectionWidth.current());
  607. if (rtl()) {
  608. selx = width() - selx - selw;
  609. }
  610. const auto sely = _iconsTop;
  611. const auto area = st().iconArea;
  612. auto rect = QRect(
  613. QPoint(selx, sely) + _areaPosition,
  614. QSize(selw - 2 * _areaPosition.x(), area));
  615. if (context.expanding) {
  616. const auto recthalf = rect.height() / 2;
  617. const auto myhalf = height() / 2;
  618. const auto sub = anim::interpolate(recthalf, 0, context.progress);
  619. const auto shift = anim::interpolate(myhalf, 0, context.progress);
  620. rect = rect.marginsRemoved(
  621. { sub, sub, sub, sub }
  622. ).translated(0, shift);
  623. }
  624. if (rect.width() == rect.height() || _subiconsWidth <= _singleWidth) {
  625. _selectionBg.paint(p, rect);
  626. } else if (selw == _subiconsWidth) {
  627. _subselectionBg.paint(p, rect);
  628. } else {
  629. PainterHighQualityEnabler hq(p);
  630. const auto progress = (selw - _singleWidth)
  631. / float64(_subiconsWidth - _singleWidth);
  632. const auto radius = anim::interpolate(
  633. st::roundRadiusLarge,
  634. area / 2,
  635. progress);
  636. p.setPen(Qt::NoPen);
  637. p.setBrush(st().categoriesBgOver);
  638. p.drawRoundedRect(rect, radius, radius);
  639. }
  640. }
  641. void StickersListFooter::paintLeftRightFading(
  642. QPainter &p,
  643. const ExpandingContext &context) const {
  644. const auto o_left_normal = std::clamp(
  645. _iconState.x.current() / st().fadeLeft.width(),
  646. 0.,
  647. 1.);
  648. const auto o_left = context.expanding
  649. ? (1. - context.progress * (1. - o_left_normal))
  650. : o_left_normal;
  651. const auto radiusSkip = context.expanding
  652. ? std::max(context.radius - st::emojiPanRadius, 0)
  653. : 0;
  654. if (o_left > 0) {
  655. p.setOpacity(o_left);
  656. const auto left = std::max(_iconsLeft, radiusSkip);
  657. const auto top = _iconsTop;
  658. if (left >= st::emojiPanRadius) {
  659. st().fadeLeft.fill(
  660. p,
  661. QRect(left, top, st().fadeLeft.width(), st().footer));
  662. } else {
  663. validateFadeLeft(left + st().fadeLeft.width());
  664. p.drawImage(0, _iconsTop, _fadeLeftCache);
  665. }
  666. p.setOpacity(1.);
  667. }
  668. const auto o_right_normal = std::clamp(
  669. (_iconState.max - _iconState.x.current()) / st().fadeRight.width(),
  670. 0.,
  671. 1.);
  672. const auto o_right = context.expanding
  673. ? (1. - context.progress * (1. - o_right_normal))
  674. : o_right_normal;
  675. if (o_right > 0) {
  676. p.setOpacity(o_right);
  677. const auto right = std::max(_iconsRight, radiusSkip);
  678. const auto rightWidth = right + st().fadeRight.width();
  679. if (right >= st::emojiPanRadius) {
  680. st().fadeRight.fill(
  681. p,
  682. QRect(
  683. width() - rightWidth,
  684. _iconsTop,
  685. st().fadeRight.width(),
  686. st().footer));
  687. } else {
  688. validateFadeRight(rightWidth);
  689. p.drawImage(width() - rightWidth, _iconsTop, _fadeRightCache);
  690. }
  691. p.setOpacity(1.);
  692. }
  693. }
  694. void StickersListFooter::validateFadeLeft(int leftWidth) const {
  695. validateFadeMask();
  696. const auto ratio = devicePixelRatioF();
  697. const auto &color = st().categoriesBg->c;
  698. dpr::Validate(_fadeLeftCache, ratio, { leftWidth, st().footer }, [&](
  699. QPainter &p,
  700. QSize size) {
  701. _fadeLeftColor = color;
  702. const auto frame = dpr::IconFrame(st().fadeLeft, color, ratio);
  703. p.drawImage(
  704. QRect(
  705. size.width() - frame.width(),
  706. 0,
  707. frame.width(),
  708. size.height()),
  709. frame);
  710. p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
  711. p.drawImage(0, 0, _fadeMask);
  712. }, (_fadeLeftColor != color), Qt::transparent);
  713. }
  714. void StickersListFooter::validateFadeRight(int rightWidth) const {
  715. validateFadeMask();
  716. const auto ratio = devicePixelRatioF();
  717. const auto &color = st().categoriesBg->c;
  718. dpr::Validate(_fadeRightCache, ratio, { rightWidth, st().footer }, [&](
  719. QPainter &p,
  720. QSize size) {
  721. _fadeRightColor = color;
  722. const auto frame = dpr::IconFrame(st().fadeRight, color, ratio);
  723. p.drawImage(QRect(0, 0, frame.width(), size.height()), frame);
  724. p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
  725. p.drawImage(size.width() - _fadeMask.width(), 0, _fadeMask);
  726. }, (_fadeRightColor != color), Qt::transparent);
  727. }
  728. void StickersListFooter::validateFadeMask() const {
  729. const auto ratio = devicePixelRatioF();
  730. const auto width = st().fadeLeft.width()
  731. + st().fadeRight.width()
  732. + 2 * st::emojiPanRadius;
  733. dpr::Validate(_fadeMask, ratio, { width, st().footer }, [&](
  734. QPainter &p,
  735. QSize size) {
  736. const auto radius = st::emojiPanRadius * ratio;
  737. p.setBrush(Qt::white);
  738. p.setPen(Qt::NoPen);
  739. auto hq = PainterHighQualityEnabler(p);
  740. p.drawRoundedRect(QRect(QPoint(), size), radius, radius);
  741. }, false, Qt::transparent, false);
  742. }
  743. void StickersListFooter::resizeEvent(QResizeEvent *e) {
  744. refreshIconsGeometry(_activeByScrollId, ValidateIconAnimations::None);
  745. }
  746. rpl::producer<uint64> StickersListFooter::setChosen() const {
  747. return _setChosen.events();
  748. }
  749. rpl::producer<> StickersListFooter::openSettingsRequests() const {
  750. return _openSettingsRequests.events();
  751. }
  752. void StickersListFooter::mousePressEvent(QMouseEvent *e) {
  753. if (e->button() != Qt::LeftButton) {
  754. return;
  755. }
  756. _iconsMousePos = e->globalPos();
  757. updateSelected();
  758. if (_selected == SpecialOver::Settings) {
  759. _openSettingsRequests.fire({});
  760. } else {
  761. _pressed = _selected;
  762. _iconsMouseDown = _iconsMousePos;
  763. _iconState.draggingStartX = qRound(_iconState.x.current());
  764. _subiconState.draggingStartX = qRound(_subiconState.x.current());
  765. }
  766. }
  767. void StickersListFooter::mouseMoveEvent(QMouseEvent *e) {
  768. _iconsMousePos = e ? e->globalPos() : QCursor::pos();
  769. updateSelected();
  770. if (!_iconState.dragging
  771. && !_icons.empty()
  772. && v::is<IconId>(_pressed)) {
  773. if ((_iconsMousePos - _iconsMouseDown).manhattanLength() >= QApplication::startDragDistance()) {
  774. const auto &icon = _icons[v::get<IconId>(_pressed).index];
  775. (icon.setId == AllEmojiSectionSetId()
  776. ? _subiconState
  777. : _iconState).dragging = true;
  778. }
  779. }
  780. checkDragging(_iconState);
  781. checkDragging(_subiconState);
  782. }
  783. void StickersListFooter::checkDragging(ScrollState &state) {
  784. if (state.dragging) {
  785. const auto newX = std::clamp(
  786. (rtl() ? -1 : 1) * (_iconsMouseDown.x() - _iconsMousePos.x())
  787. + state.draggingStartX,
  788. 0,
  789. state.max);
  790. if (newX != qRound(state.x.current())) {
  791. state.x = anim::value(newX, newX);
  792. state.animationStart = 0;
  793. state.animation.stop();
  794. update();
  795. }
  796. }
  797. }
  798. void StickersListFooter::mouseReleaseEvent(QMouseEvent *e) {
  799. if (_icons.empty()) {
  800. return;
  801. }
  802. const auto wasDown = std::exchange(_pressed, SpecialOver::None);
  803. _iconsMousePos = e ? e->globalPos() : QCursor::pos();
  804. if (finishDragging()) {
  805. return;
  806. }
  807. updateSelected();
  808. if (wasDown == _selected) {
  809. if (const auto icon = std::get_if<IconId>(&_selected)) {
  810. const auto info = iconInfo(icon->index);
  811. _iconState.selectionX = anim::value(info.left, info.left);
  812. _iconState.selectionWidth = anim::value(info.width, info.width);
  813. const auto setId = _icons[icon->index].setId;
  814. _setChosen.fire_copy((setId == AllEmojiSectionSetId())
  815. ? EmojiSectionSetId(
  816. EmojiSection(int(EmojiSection::People) + icon->subindex))
  817. : setId);
  818. }
  819. }
  820. }
  821. bool StickersListFooter::finishDragging() {
  822. const auto icon = finishDragging(_iconState);
  823. const auto subicon = finishDragging(_subiconState);
  824. return icon || subicon;
  825. }
  826. bool StickersListFooter::finishDragging(ScrollState &state) {
  827. if (!state.dragging) {
  828. return false;
  829. }
  830. const auto newX = std::clamp(
  831. state.draggingStartX + _iconsMouseDown.x() - _iconsMousePos.x(),
  832. 0,
  833. state.max);
  834. if (newX != qRound(state.x.current())) {
  835. state.x = anim::value(newX, newX);
  836. state.animationStart = 0;
  837. state.animation.stop();
  838. update();
  839. }
  840. state.dragging = false;
  841. updateSelected();
  842. return true;
  843. }
  844. bool StickersListFooter::eventHook(QEvent *e) {
  845. if (e->type() == QEvent::TouchBegin) {
  846. } else if (e->type() == QEvent::Wheel) {
  847. if (!_icons.empty()
  848. && v::is<IconId>(_selected)
  849. && (_pressed == SpecialOver::None)) {
  850. scrollByWheelEvent(static_cast<QWheelEvent*>(e));
  851. }
  852. }
  853. return InnerFooter::eventHook(e);
  854. }
  855. void StickersListFooter::scrollByWheelEvent(
  856. not_null<QWheelEvent*> e) {
  857. auto horizontal = (e->angleDelta().x() != 0);
  858. auto vertical = (e->angleDelta().y() != 0);
  859. if (!horizontal && !vertical) {
  860. return;
  861. }
  862. auto delta = horizontal
  863. ? ((rtl() ? -1 : 1) * (e->pixelDelta().x()
  864. ? e->pixelDelta().x()
  865. : e->angleDelta().x()))
  866. : (e->pixelDelta().y()
  867. ? e->pixelDelta().y()
  868. : e->angleDelta().y());
  869. const auto use = [&](ScrollState &state) {
  870. const auto now = qRound(state.x.current());
  871. const auto used = now - delta;
  872. const auto next = std::clamp(used, 0, state.max);
  873. delta = next - used;
  874. if (next != now) {
  875. state.x = anim::value(next, next);
  876. state.animationStart = 0;
  877. state.animation.stop();
  878. updateSelected();
  879. update();
  880. }
  881. };
  882. const auto index = v::get<IconId>(_selected).index;
  883. if (_subiconsExpanded
  884. && _icons[index].setId == AllEmojiSectionSetId()) {
  885. use(_subiconState);
  886. } else {
  887. use(_iconState);
  888. }
  889. }
  890. void StickersListFooter::clipCallback(
  891. Media::Clip::Notification notification,
  892. uint64 setId) {
  893. using namespace Media::Clip;
  894. switch (notification) {
  895. case Notification::Reinit: {
  896. enumerateIcons([&](const IconInfo &info) {
  897. auto &icon = _icons[info.index];
  898. if (icon.setId != setId || !icon.webm) {
  899. return true;
  900. } else if (icon.webm->state() == State::Error) {
  901. icon.webm.setBad();
  902. } else if (!info.visible) {
  903. icon.webm = nullptr;
  904. } else if (icon.webm->ready() && !icon.webm->started()) {
  905. icon.webm->start({
  906. .frame = { icon.pixw, icon.pixh },
  907. .keepAlpha = true,
  908. });
  909. }
  910. updateSetIconAt(info.adjustedLeft);
  911. return true;
  912. });
  913. } break;
  914. case Notification::Repaint:
  915. updateSetIcon(setId);
  916. break;
  917. }
  918. }
  919. void StickersListFooter::updateSelected() {
  920. if (_pressed != SpecialOver::None) {
  921. return;
  922. }
  923. auto p = mapFromGlobal(_iconsMousePos);
  924. auto x = p.x(), y = p.y();
  925. if (rtl()) x = width() - x;
  926. const auto settingsLeft = _iconsLeft - _singleWidth;
  927. auto newOver = OverState(SpecialOver::None);
  928. if (_features.stickersSettings
  929. && x >= settingsLeft
  930. && x < settingsLeft + _singleWidth
  931. && y >= _iconsTop
  932. && y < _iconsTop + st().footer) {
  933. if (!_icons.empty()) {
  934. newOver = SpecialOver::Settings;
  935. }
  936. } else if (!_icons.empty()) {
  937. if (y >= _iconsTop
  938. && y < _iconsTop + st().footer
  939. && x >= _iconsLeft
  940. && x < width() - _iconsRight) {
  941. enumerateIcons([&](const IconInfo &info) {
  942. if (x >= info.adjustedLeft
  943. && x < info.adjustedLeft + info.width) {
  944. newOver = IconId{ .index = info.index };
  945. if (_icons[info.index].setId == AllEmojiSectionSetId()) {
  946. const auto subx = (x - info.adjustedLeft);
  947. enumerateSubicons([&](const IconInfo &info) {
  948. if (subx >= info.adjustedLeft
  949. && subx < info.adjustedLeft + info.width) {
  950. v::get<IconId>(newOver).subindex = info.index;
  951. return false;
  952. }
  953. return true;
  954. });
  955. }
  956. return false;
  957. }
  958. return true;
  959. });
  960. }
  961. }
  962. if (newOver != _selected) {
  963. if (newOver == SpecialOver::None) {
  964. setCursor(style::cur_default);
  965. } else if (_selected == SpecialOver::None) {
  966. setCursor(style::cur_pointer);
  967. }
  968. _selected = newOver;
  969. }
  970. }
  971. auto StickersListFooter::getLottieRenderer()
  972. -> std::shared_ptr<Lottie::FrameRenderer> {
  973. if (auto result = _lottieRenderer.lock()) {
  974. return result;
  975. }
  976. auto result = Lottie::MakeFrameRenderer();
  977. _lottieRenderer = result;
  978. return result;
  979. }
  980. void StickersListFooter::refreshIcons(
  981. std::vector<StickerIcon> icons,
  982. uint64 activeSetId,
  983. Fn<std::shared_ptr<Lottie::FrameRenderer>()> renderer,
  984. ValidateIconAnimations animations) {
  985. _renderer = renderer
  986. ? std::move(renderer)
  987. : [=] { return getLottieRenderer(); };
  988. auto indices = base::flat_map<uint64, int>();
  989. indices.reserve(_icons.size());
  990. auto index = 0;
  991. for (const auto &entry : _icons) {
  992. indices.emplace(entry.setId, index++);
  993. }
  994. for (auto &now : icons) {
  995. if (const auto i = indices.find(now.setId); i != end(indices)) {
  996. auto &was = _icons[i->second];
  997. if (now.sticker == was.sticker) {
  998. now.webm = std::move(was.webm);
  999. now.lottie = std::move(was.lottie);
  1000. now.custom = std::move(was.custom);
  1001. now.lifetime = std::move(was.lifetime);
  1002. now.savedFrame = std::move(was.savedFrame);
  1003. }
  1004. }
  1005. }
  1006. _icons = std::move(icons);
  1007. refreshIconsGeometry(activeSetId, animations);
  1008. }
  1009. void StickersListFooter::refreshScrollableDimensions() {
  1010. const auto &last = iconInfo(_icons.size() - 1);
  1011. _iconState.max = std::max(
  1012. last.left + last.width + _iconsLeft + _iconsRight - width(),
  1013. 0);
  1014. if (_iconState.x.current() > _iconState.max) {
  1015. _iconState.x = anim::value(_iconState.max, _iconState.max);
  1016. }
  1017. }
  1018. void StickersListFooter::refreshIconsGeometry(
  1019. uint64 activeSetId,
  1020. ValidateIconAnimations animations) {
  1021. _selected = _pressed = SpecialOver::None;
  1022. _iconState.x.finish();
  1023. _iconState.selectionX.finish();
  1024. _iconState.selectionWidth.finish();
  1025. _iconState.animationStart = 0;
  1026. _iconState.animation.stop();
  1027. if (_icons.size() > 1
  1028. && _icons[1].setId == EmojiSectionSetId(EmojiSection::People)) {
  1029. _singleWidth = (width() - _iconsLeft - _iconsRight) / _icons.size();
  1030. } else {
  1031. _singleWidth = st().iconWidth;
  1032. }
  1033. _areaPosition = QPoint(
  1034. (_singleWidth - st().iconArea) / 2,
  1035. (st().footer - st().iconArea) / 2);
  1036. refreshScrollableDimensions();
  1037. refreshSubiconsGeometry();
  1038. _iconState.selected = _subiconState.selected = -2;
  1039. validateSelectedIcon(activeSetId, animations);
  1040. update();
  1041. }
  1042. void StickersListFooter::refreshSubiconsGeometry() {
  1043. using Section = Ui::Emoji::Section;
  1044. _subiconState.x.finish();
  1045. _subiconState.animationStart = 0;
  1046. _subiconState.animation.stop();
  1047. const auto half = _singleWidth / 2;
  1048. const auto count = int(Section::Symbols) - int(Section::Recent);
  1049. const auto widthMax = count * _singleWidth;
  1050. const auto widthMin = 5 * _singleWidth + half;
  1051. const auto collapsedWidth = int(_icons.size()) * _singleWidth;
  1052. _subiconsWidth = std::clamp(
  1053. width() + _singleWidth - collapsedWidth,
  1054. widthMin,
  1055. widthMax);
  1056. if (_subiconsWidth < widthMax) {
  1057. _subiconsWidth = half
  1058. + (((_subiconsWidth - half) / _singleWidth) * _singleWidth);
  1059. }
  1060. _subiconState.max = std::max(
  1061. widthMax - _subiconsWidth,
  1062. 0);
  1063. if (_subiconState.x.current() > _subiconState.max) {
  1064. _subiconState.x = anim::value(_subiconState.max, _subiconState.max);
  1065. }
  1066. updateEmojiWidthCallback();
  1067. }
  1068. void StickersListFooter::paintStickerSettingsIcon(QPainter &p) const {
  1069. const auto settingsLeft = _iconsLeft - _singleWidth;
  1070. st().icons.settings.paint(
  1071. p,
  1072. (settingsLeft + (_singleWidth - st().icons.settings.width()) / 2),
  1073. _iconsTop + st::emojiCategoryIconTop,
  1074. width());
  1075. }
  1076. void StickersListFooter::customEmojiRepaint() {
  1077. if (!_repaintScheduled) {
  1078. _repaintScheduled = true;
  1079. update();
  1080. }
  1081. }
  1082. void StickersListFooter::validateIconLottieAnimation(
  1083. const StickerIcon &icon) {
  1084. icon.ensureMediaCreated();
  1085. if (icon.lottie
  1086. || !icon.sticker
  1087. || !HasLottieThumbnail(
  1088. icon.set ? icon.set->thumbnailType() : StickerType(),
  1089. icon.thumbnailMedia.get(),
  1090. icon.stickerMedia.get())) {
  1091. return;
  1092. }
  1093. auto player = LottieThumbnail(
  1094. icon.thumbnailMedia.get(),
  1095. icon.stickerMedia.get(),
  1096. StickerLottieSize::StickersFooter,
  1097. QSize(icon.pixw, icon.pixh) * style::DevicePixelRatio(),
  1098. _renderer());
  1099. if (!player) {
  1100. return;
  1101. }
  1102. icon.lottie = std::move(player);
  1103. const auto id = icon.setId;
  1104. icon.lottie->updates(
  1105. ) | rpl::start_with_next([=] {
  1106. updateSetIcon(id);
  1107. }, icon.lifetime);
  1108. }
  1109. void StickersListFooter::validateIconWebmAnimation(
  1110. const StickerIcon &icon) {
  1111. icon.ensureMediaCreated();
  1112. if (icon.webm
  1113. || !icon.sticker
  1114. || !HasWebmThumbnail(
  1115. icon.set ? icon.set->thumbnailType() : StickerType(),
  1116. icon.thumbnailMedia.get(),
  1117. icon.stickerMedia.get())) {
  1118. return;
  1119. }
  1120. const auto id = icon.setId;
  1121. auto callback = [=](Media::Clip::Notification notification) {
  1122. clipCallback(notification, id);
  1123. };
  1124. icon.webm = WebmThumbnail(
  1125. icon.thumbnailMedia.get(),
  1126. icon.stickerMedia.get(),
  1127. std::move(callback));
  1128. }
  1129. void StickersListFooter::validateIconAnimation(
  1130. const StickerIcon &icon) {
  1131. const auto emoji = icon.sticker;
  1132. if (emoji && emoji->sticker()->setType == Data::StickersType::Emoji) {
  1133. if (!icon.custom) {
  1134. const auto tag = Data::CustomEmojiManager::SizeTag::SetIcon;
  1135. auto &manager = emoji->owner().customEmojiManager();
  1136. icon.custom = manager.create(
  1137. emoji->id,
  1138. [=] { customEmojiRepaint(); },
  1139. tag);
  1140. }
  1141. return;
  1142. }
  1143. validateIconWebmAnimation(icon);
  1144. validateIconLottieAnimation(icon);
  1145. }
  1146. void StickersListFooter::updateSetIcon(uint64 setId) {
  1147. enumerateVisibleIcons([&](const IconInfo &info) {
  1148. if (_icons[info.index].setId != setId) {
  1149. return;
  1150. }
  1151. updateSetIconAt(info.adjustedLeft);
  1152. });
  1153. }
  1154. void StickersListFooter::updateSetIconAt(int left) {
  1155. update(left, _iconsTop, _singleWidth, st().footer);
  1156. }
  1157. void StickersListFooter::paintSetIcon(
  1158. Painter &p,
  1159. const ExpandingContext &context,
  1160. const IconInfo &info,
  1161. crl::time now,
  1162. bool paused) const {
  1163. const auto &icon = _icons[info.index];
  1164. const auto expandingShift = context.expanding
  1165. ? QPoint(
  1166. 0,
  1167. anim::interpolate(height() / 2, 0, context.progress))
  1168. : QPoint();
  1169. if (icon.sticker) {
  1170. icon.ensureMediaCreated();
  1171. const_cast<StickersListFooter*>(this)->validateIconAnimation(icon);
  1172. }
  1173. if (context.expanding) {
  1174. if (icon.custom) {
  1175. p.translate(expandingShift);
  1176. } else {
  1177. p.save();
  1178. const auto center = QPoint(
  1179. info.adjustedLeft + _singleWidth / 2,
  1180. _iconsTop + st().footer / 2);
  1181. p.translate(expandingShift + center);
  1182. p.scale(context.progress, context.progress);
  1183. p.translate(-center);
  1184. }
  1185. }
  1186. if (icon.sticker) {
  1187. prepareSetIcon(context, info, now, paused);
  1188. p.drawImage(info.adjustedLeft, _iconsTop, _setIconCache);
  1189. } else {
  1190. p.translate(info.adjustedLeft, _iconsTop);
  1191. paintSetIconToCache(p, context, info, now, paused);
  1192. p.translate(-info.adjustedLeft, -_iconsTop);
  1193. }
  1194. if (context.expanding) {
  1195. if (icon.custom) {
  1196. p.translate(-expandingShift);
  1197. } else {
  1198. p.restore();
  1199. }
  1200. }
  1201. }
  1202. void StickersListFooter::prepareSetIcon(
  1203. const ExpandingContext &context,
  1204. const IconInfo &info,
  1205. crl::time now,
  1206. bool paused) const {
  1207. _setIconCache.fill(Qt::transparent);
  1208. auto p = Painter(&_setIconCache);
  1209. paintSetIconToCache(p, context, info, now, paused);
  1210. if (!_icons[info.index].sticker) {
  1211. return;
  1212. }
  1213. // Rounding the corners.
  1214. auto hq = PainterHighQualityEnabler(p);
  1215. p.setCompositionMode(QPainter::CompositionMode_Source);
  1216. p.setBrush(Qt::NoBrush);
  1217. auto pen = QPen(Qt::transparent);
  1218. pen.setWidth(style::ConvertScaleExact(4.));
  1219. p.setPen(pen);
  1220. const auto area = st().iconArea;
  1221. auto rect = QRect(_areaPosition, QSize(area, area));
  1222. p.drawRoundedRect(rect, st::emojiPanRadius, st::emojiPanRadius);
  1223. }
  1224. void StickersListFooter::paintSetIconToCache(
  1225. Painter &p,
  1226. const ExpandingContext &context,
  1227. const IconInfo &info,
  1228. crl::time now,
  1229. bool paused) const {
  1230. const auto &icon = _icons[info.index];
  1231. if (icon.sticker) {
  1232. const auto origin = icon.sticker->stickerSetOrigin();
  1233. const auto thumb = icon.thumbnailMedia
  1234. ? icon.thumbnailMedia->image()
  1235. : icon.stickerMedia
  1236. ? icon.stickerMedia->thumbnail()
  1237. : nullptr;
  1238. const auto x = (_singleWidth - icon.pixw) / 2;
  1239. const auto y = (st().footer - icon.pixh) / 2;
  1240. if (icon.custom) {
  1241. icon.custom->paint(p, Ui::Text::CustomEmoji::Context{
  1242. .textColor = (_customTextColor
  1243. ? _customTextColor()
  1244. : st().textFg->c),
  1245. .size = QSize(icon.pixw, icon.pixh),
  1246. .now = now,
  1247. .scale = context.progress,
  1248. .position = { x, y },
  1249. .paused = paused,
  1250. .scaled = context.expanding,
  1251. .internal = { .forceFirstFrame = _forceFirstFrame },
  1252. });
  1253. } else if (icon.lottie && icon.lottie->ready()) {
  1254. const auto frame = icon.lottie->frame();
  1255. const auto size = frame.size() / style::DevicePixelRatio();
  1256. if (icon.savedFrame.isNull()) {
  1257. icon.savedFrame = frame;
  1258. icon.savedFrame.setDevicePixelRatio(
  1259. style::DevicePixelRatio());
  1260. }
  1261. p.drawImage(
  1262. QRect(
  1263. (_singleWidth - size.width()) / 2,
  1264. (st().footer - size.height()) / 2,
  1265. size.width(),
  1266. size.height()),
  1267. frame);
  1268. if (!paused) {
  1269. icon.lottie->markFrameShown();
  1270. }
  1271. } else if (icon.webm && icon.webm->started()) {
  1272. const auto frame = icon.webm->current(
  1273. { .frame = { icon.pixw, icon.pixh }, .keepAlpha = true },
  1274. paused ? 0 : now);
  1275. if (icon.savedFrame.isNull()) {
  1276. icon.savedFrame = frame;
  1277. icon.savedFrame.setDevicePixelRatio(
  1278. style::DevicePixelRatio());
  1279. }
  1280. p.drawImage(x, y, frame);
  1281. } else if (!icon.savedFrame.isNull()) {
  1282. p.drawImage(x, y, icon.savedFrame);
  1283. } else if (thumb) {
  1284. const auto pixmap = (!icon.lottie && thumb)
  1285. ? thumb->pix(icon.pixw, icon.pixh)
  1286. : QPixmap();
  1287. if (pixmap.isNull()) {
  1288. return;
  1289. } else if (icon.savedFrame.isNull()) {
  1290. icon.savedFrame = pixmap.toImage();
  1291. }
  1292. p.drawPixmapLeft(x, y, width(), pixmap);
  1293. }
  1294. } else if (icon.megagroup) {
  1295. const auto size = st::stickerGroupCategorySize;
  1296. icon.megagroup->paintUserpicLeft(
  1297. p,
  1298. icon.megagroupUserpic,
  1299. (_singleWidth - size) / 2,
  1300. (st().footer - size) / 2,
  1301. width(),
  1302. st::stickerGroupCategorySize);
  1303. } else {
  1304. using Section = Ui::Emoji::Section;
  1305. const auto sectionIcon = [&](Section section, bool active) {
  1306. const auto icons = std::array{
  1307. &st().icons.recent,
  1308. &st().icons.recentActive,
  1309. &st().icons.people,
  1310. &st().icons.peopleActive,
  1311. &st().icons.nature,
  1312. &st().icons.natureActive,
  1313. &st().icons.food,
  1314. &st().icons.foodActive,
  1315. &st().icons.activity,
  1316. &st().icons.activityActive,
  1317. &st().icons.travel,
  1318. &st().icons.travelActive,
  1319. &st().icons.objects,
  1320. &st().icons.objectsActive,
  1321. &st().icons.symbols,
  1322. &st().icons.symbolsActive,
  1323. };
  1324. const auto index = int(section) * 2 + (active ? 1 : 0);
  1325. Assert(index >= 0 && index < icons.size());
  1326. return icons[index];
  1327. };
  1328. const auto paintOne = [&](int left, const style::icon *icon) {
  1329. left += (_singleWidth - icon->width()) / 2;
  1330. const auto top = (st().footer - icon->height()) / 2;
  1331. if (_customTextColor) {
  1332. icon->paint(p, left, top, width(), _customTextColor());
  1333. } else {
  1334. icon->paint(p, left, top, width());
  1335. }
  1336. };
  1337. if (_icons[info.index].setId == AllEmojiSectionSetId()
  1338. && info.width > _singleWidth) {
  1339. const auto skip = st::emojiIconSelectSkip;
  1340. p.save();
  1341. p.setClipRect(
  1342. skip,
  1343. _iconsTop,
  1344. info.width - 2 * skip,
  1345. st().footer,
  1346. Qt::IntersectClip);
  1347. enumerateSubicons([&](const IconInfo &info) {
  1348. if (info.visible) {
  1349. paintOne(
  1350. info.adjustedLeft,
  1351. sectionIcon(
  1352. Section(int(Section::People) + info.index),
  1353. (_subiconState.selected == info.index)));
  1354. }
  1355. return true;
  1356. });
  1357. p.restore();
  1358. } else {
  1359. paintOne(0, [&] {
  1360. const auto selected = (info.index == _iconState.selected);
  1361. if (icon.setId == AllEmojiSectionSetId()) {
  1362. return &st().icons.people;
  1363. } else if (const auto section = SetIdEmojiSection(icon.setId)) {
  1364. return sectionIcon(*section, selected);
  1365. } else if (icon.setId == Data::Stickers::CollectibleSetId) {
  1366. return &st().icons.collectibles;
  1367. }
  1368. return sectionIcon(Section::Recent, selected);
  1369. }());
  1370. }
  1371. }
  1372. }
  1373. LocalStickersManager::LocalStickersManager(not_null<Main::Session*> session)
  1374. : _session(session)
  1375. , _api(&session->mtp()) {
  1376. }
  1377. void LocalStickersManager::install(uint64 setId) {
  1378. const auto &sets = _session->data().stickers().sets();
  1379. const auto it = sets.find(setId);
  1380. if (it == sets.cend()) {
  1381. return;
  1382. }
  1383. const auto set = it->second.get();
  1384. const auto input = set->mtpInput();
  1385. if (!(set->flags & Data::StickersSetFlag::NotLoaded)
  1386. && !set->stickers.empty()) {
  1387. sendInstallRequest(setId, input);
  1388. return;
  1389. }
  1390. _api.request(MTPmessages_GetStickerSet(
  1391. input,
  1392. MTP_int(0) // hash
  1393. )).done([=](const MTPmessages_StickerSet &result) {
  1394. result.match([&](const MTPDmessages_stickerSet &data) {
  1395. _session->data().stickers().feedSetFull(data);
  1396. }, [](const MTPDmessages_stickerSetNotModified &) {
  1397. LOG(("API Error: Unexpected messages.stickerSetNotModified."));
  1398. });
  1399. sendInstallRequest(setId, input);
  1400. }).send();
  1401. }
  1402. bool LocalStickersManager::isInstalledLocally(uint64 setId) const {
  1403. return _installedLocallySets.contains(setId);
  1404. }
  1405. void LocalStickersManager::sendInstallRequest(
  1406. uint64 setId,
  1407. const MTPInputStickerSet &input) {
  1408. _api.request(MTPmessages_InstallStickerSet(
  1409. input,
  1410. MTP_bool(false)
  1411. )).done([=](const MTPmessages_StickerSetInstallResult &result) {
  1412. if (result.type() == mtpc_messages_stickerSetInstallResultArchive) {
  1413. _session->data().stickers().applyArchivedResult(
  1414. result.c_messages_stickerSetInstallResultArchive());
  1415. }
  1416. }).fail([=] {
  1417. notInstalledLocally(setId);
  1418. _session->data().stickers().undoInstallLocally(setId);
  1419. }).send();
  1420. installedLocally(setId);
  1421. _session->data().stickers().installLocally(setId);
  1422. }
  1423. void LocalStickersManager::installedLocally(uint64 setId) {
  1424. _installedLocallySets.insert(setId);
  1425. }
  1426. void LocalStickersManager::notInstalledLocally(uint64 setId) {
  1427. _installedLocallySets.remove(setId);
  1428. }
  1429. void LocalStickersManager::removeInstalledLocally(uint64 setId) {
  1430. _installedLocallySets.remove(setId);
  1431. }
  1432. bool LocalStickersManager::clearInstalledLocally() {
  1433. if (_installedLocallySets.empty()) {
  1434. return false;
  1435. }
  1436. _installedLocallySets.clear();
  1437. return true;
  1438. }
  1439. } // namespace ChatHelpers