sticker_set_box.cpp 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156
  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 "boxes/sticker_set_box.h"
  8. #include "api/api_common.h"
  9. #include "api/api_toggling_media.h"
  10. #include "apiwrap.h"
  11. #include "base/unixtime.h"
  12. #include "boxes/premium_preview_box.h"
  13. #include "chat_helpers/compose/compose_show.h"
  14. #include "chat_helpers/stickers_list_widget.h"
  15. #include "chat_helpers/stickers_lottie.h"
  16. #include "core/application.h"
  17. #include "data/data_document.h"
  18. #include "data/data_document_media.h"
  19. #include "data/data_file_origin.h"
  20. #include "data/data_peer_values.h"
  21. #include "data/data_session.h"
  22. #include "data/stickers/data_custom_emoji.h"
  23. #include "data/stickers/data_stickers.h"
  24. #include "dialogs/ui/dialogs_layout.h"
  25. #include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
  26. #include "lang/lang_keys.h"
  27. #include "lottie/lottie_animation.h"
  28. #include "lottie/lottie_multi_player.h"
  29. #include "main/main_session.h"
  30. #include "mainwindow.h"
  31. #include "media/clip/media_clip_reader.h"
  32. #include "menu/menu_send.h"
  33. #include "mtproto/sender.h"
  34. #include "settings/settings_premium.h"
  35. #include "storage/storage_account.h"
  36. #include "ui/boxes/confirm_box.h"
  37. #include "ui/cached_round_corners.h"
  38. #include "ui/effects/animation_value_f.h"
  39. #include "ui/effects/path_shift_gradient.h"
  40. #include "ui/emoji_config.h"
  41. #include "ui/image/image.h"
  42. #include "ui/image/image_location_factory.h"
  43. #include "ui/painter.h"
  44. #include "ui/power_saving.h"
  45. #include "ui/rect.h"
  46. #include "ui/text/custom_emoji_instance.h"
  47. #include "ui/text/text_utilities.h"
  48. #include "ui/toast/toast.h"
  49. #include "ui/vertical_list.h"
  50. #include "ui/widgets/buttons.h"
  51. #include "ui/widgets/fields/input_field.h"
  52. #include "ui/widgets/gradient_round_button.h"
  53. #include "ui/widgets/menu/menu_add_action_callback.h"
  54. #include "ui/widgets/menu/menu_add_action_callback_factory.h"
  55. #include "ui/widgets/popup_menu.h"
  56. #include "ui/widgets/scroll_area.h"
  57. #include "styles/style_layers.h"
  58. #include "styles/style_chat_helpers.h"
  59. #include "styles/style_info.h"
  60. #include "styles/style_menu_icons.h"
  61. #include "styles/style_premium.h"
  62. #include <QtWidgets/QApplication>
  63. #include <QtGui/QClipboard>
  64. #include <QtSvg/QSvgRenderer>
  65. namespace {
  66. constexpr auto kStickersPerRow = 5;
  67. constexpr auto kEmojiPerRow = 8;
  68. constexpr auto kMinRepaintDelay = crl::time(33);
  69. constexpr auto kMinAfterScrollDelay = crl::time(33);
  70. constexpr auto kGrayLockOpacity = 0.3;
  71. constexpr auto kStickerMoveDuration = crl::time(200);
  72. using Data::StickersSet;
  73. using Data::StickersPack;
  74. using SetFlag = Data::StickersSetFlag;
  75. using TLStickerSet = MTPmessages_StickerSet;
  76. [[nodiscard]] std::optional<QColor> ComputeImageColor(
  77. const style::icon &lockIcon,
  78. const QImage &frame,
  79. RectPart part) {
  80. if (frame.isNull()
  81. || frame.format() != QImage::Format_ARGB32_Premultiplied) {
  82. return {};
  83. }
  84. auto sr = int64();
  85. auto sg = int64();
  86. auto sb = int64();
  87. auto sa = int64();
  88. const auto factor = style::DevicePixelRatio();
  89. const auto size = lockIcon.size() * factor;
  90. const auto width = std::min(frame.width(), size.width());
  91. const auto height = std::min(frame.height(), size.height());
  92. const auto radius = st::roundRadiusSmall;
  93. const auto skipx = (part == RectPart::TopLeft
  94. || part == RectPart::Left
  95. || part == RectPart::BottomLeft)
  96. ? 0
  97. : (part == RectPart::Top
  98. || part == RectPart::Center
  99. || part == RectPart::Bottom)
  100. ? (frame.width() - width) / 2
  101. : std::max(frame.width() - width - radius, 0);
  102. const auto skipy = (part == RectPart::TopLeft
  103. || part == RectPart::Top
  104. || part == RectPart::TopRight)
  105. ? 0
  106. : (part == RectPart::Left
  107. || part == RectPart::Center
  108. || part == RectPart::Right)
  109. ? (frame.height() - height) / 2
  110. : std::max(frame.height() - height - radius, 0);
  111. const auto perline = frame.bytesPerLine();
  112. const auto addperline = perline - (width * 4);
  113. auto bits = static_cast<const uchar*>(frame.bits())
  114. + perline * skipy
  115. + sizeof(uint32) * skipx;
  116. for (auto y = 0; y != height; ++y) {
  117. for (auto x = 0; x != width; ++x) {
  118. sb += int(*bits++);
  119. sg += int(*bits++);
  120. sr += int(*bits++);
  121. sa += int(*bits++);
  122. }
  123. bits += addperline;
  124. }
  125. if (!sa) {
  126. return {};
  127. }
  128. return QColor(sr * 255 / sa, sg * 255 / sa, sb * 255 / sa, 255);
  129. }
  130. [[nodiscard]] QColor ComputeLockColor(
  131. const style::icon &lockIcon,
  132. const QImage &frame,
  133. RectPart part) {
  134. return ComputeImageColor(
  135. lockIcon,
  136. frame,
  137. part
  138. ).value_or(st::windowSubTextFg->c);
  139. }
  140. void ValidatePremiumLockBg(
  141. const style::icon &lockIcon,
  142. QImage &image,
  143. const QImage &frame,
  144. RectPart part) {
  145. if (!image.isNull()) {
  146. return;
  147. }
  148. const auto factor = style::DevicePixelRatio();
  149. const auto size = lockIcon.size();
  150. image = QImage(
  151. size * factor,
  152. QImage::Format_ARGB32_Premultiplied);
  153. image.setDevicePixelRatio(factor);
  154. auto p = QPainter(&image);
  155. const auto color = ComputeLockColor(lockIcon, frame, part);
  156. p.fillRect(
  157. QRect(QPoint(), size),
  158. anim::color(color, st::windowSubTextFg, kGrayLockOpacity));
  159. p.end();
  160. image = Images::Circle(std::move(image));
  161. }
  162. void ValidatePremiumStarFg(const style::icon &lockIcon, QImage &image) {
  163. if (!image.isNull()) {
  164. return;
  165. }
  166. const auto factor = style::DevicePixelRatio();
  167. const auto size = lockIcon.size();
  168. image = QImage(
  169. size * factor,
  170. QImage::Format_ARGB32_Premultiplied);
  171. image.setDevicePixelRatio(factor);
  172. image.fill(Qt::transparent);
  173. auto p = QPainter(&image);
  174. auto star = QSvgRenderer(u":/gui/icons/settings/star.svg"_q);
  175. const auto skip = size.width() / 5.;
  176. const auto outer = QRectF(QPointF(), size).marginsRemoved(
  177. { skip, skip, skip, skip });
  178. p.setBrush(st::premiumButtonFg);
  179. p.setPen(Qt::NoPen);
  180. star.render(&p, outer);
  181. }
  182. [[nodiscard]] TextForMimeData PrepareTextFromEmoji(
  183. not_null<DocumentData*> document) {
  184. const auto info = document->sticker();
  185. const auto text = info ? info->alt : QString();
  186. return {
  187. .expanded = text,
  188. .rich = {
  189. text,
  190. {
  191. EntityInText(
  192. EntityType::CustomEmoji,
  193. 0,
  194. text.size(),
  195. Data::SerializeCustomEmojiId(document))
  196. },
  197. },
  198. };
  199. }
  200. } // namespace
  201. StickerPremiumMark::StickerPremiumMark(
  202. not_null<Main::Session*> session,
  203. const style::icon &lockIcon,
  204. RectPart part)
  205. : _lockIcon(lockIcon)
  206. , _part(part) {
  207. style::PaletteChanged(
  208. ) | rpl::start_with_next([=] {
  209. _lockGray = QImage();
  210. _star = QImage();
  211. }, _lifetime);
  212. Data::AmPremiumValue(
  213. session
  214. ) | rpl::start_with_next([=](bool premium) {
  215. _premium = premium;
  216. }, _lifetime);
  217. }
  218. void StickerPremiumMark::paint(
  219. QPainter &p,
  220. const QImage &frame,
  221. QImage &backCache,
  222. QPoint position,
  223. QSize singleSize,
  224. int outerWidth) {
  225. validateLock(frame, backCache);
  226. const auto &bg = frame.isNull() ? _lockGray : backCache;
  227. const auto factor = style::DevicePixelRatio();
  228. const auto radius = st::roundRadiusSmall;
  229. const auto shiftx = (_part == RectPart::Center)
  230. ? (singleSize.width() - (bg.width() / factor)) / 2
  231. : (singleSize.width() - (bg.width() / factor) - radius);
  232. const auto shifty = (_part == RectPart::Center)
  233. ? (singleSize.height() - (bg.height() / factor)) / 2
  234. : (singleSize.height() - (bg.height() / factor) - radius);
  235. const auto point = position + QPoint(shiftx, shifty);
  236. p.drawImage(point, bg);
  237. if (_premium && _part != RectPart::Center) {
  238. validateStar();
  239. p.drawImage(point, _star);
  240. } else {
  241. _lockIcon.paint(p, point, outerWidth);
  242. }
  243. }
  244. void StickerPremiumMark::validateLock(
  245. const QImage &frame,
  246. QImage &backCache) {
  247. auto &image = frame.isNull() ? _lockGray : backCache;
  248. ValidatePremiumLockBg(_lockIcon, image, frame, _part);
  249. }
  250. void StickerPremiumMark::validateStar() {
  251. ValidatePremiumStarFg(_lockIcon, _star);
  252. }
  253. class StickerSetBox::Inner final : public Ui::RpWidget {
  254. public:
  255. Inner(
  256. QWidget *parent,
  257. std::shared_ptr<ChatHelpers::Show> show,
  258. const StickerSetIdentifier &set,
  259. Data::StickersType type);
  260. [[nodiscard]] bool loaded() const;
  261. [[nodiscard]] bool notInstalled() const;
  262. [[nodiscard]] bool premiumEmojiSet() const;
  263. [[nodiscard]] bool official() const;
  264. [[nodiscard]] rpl::producer<TextWithEntities> title() const;
  265. [[nodiscard]] QString shortName() const;
  266. [[nodiscard]] bool isEmojiSet() const;
  267. [[nodiscard]] uint64 setId() const;
  268. void install();
  269. [[nodiscard]] rpl::producer<uint64> setInstalled() const;
  270. [[nodiscard]] rpl::producer<uint64> setArchived() const;
  271. [[nodiscard]] rpl::producer<> updateControls() const;
  272. void setReorderState(bool enabled) {
  273. _dragging.enabled = enabled;
  274. if (enabled) {
  275. _shakeAnimation.init([=] { update(); });
  276. _shakeAnimation.start();
  277. } else {
  278. _shakeAnimation.stop();
  279. update();
  280. }
  281. }
  282. [[nodiscard]] bool reorderState() const {
  283. return _dragging.enabled;
  284. }
  285. [[nodiscard]] rpl::producer<Error> errors() const;
  286. void archiveStickers();
  287. [[nodiscard]] Data::StickersType setType() const {
  288. return (_setFlags & SetFlag::Emoji)
  289. ? Data::StickersType::Emoji
  290. : (_setFlags & SetFlag::Masks)
  291. ? Data::StickersType::Masks
  292. : Data::StickersType::Stickers;
  293. }
  294. [[nodiscard]] bool amSetCreator() const {
  295. return _amSetCreator;
  296. }
  297. void applySet(const TLStickerSet &set);
  298. ~Inner();
  299. protected:
  300. void mousePressEvent(QMouseEvent *e) override;
  301. void mouseMoveEvent(QMouseEvent *e) override;
  302. void mouseReleaseEvent(QMouseEvent *e) override;
  303. void contextMenuEvent(QContextMenuEvent *e) override;
  304. void paintEvent(QPaintEvent *e) override;
  305. void leaveEventHook(QEvent *e) override;
  306. private:
  307. struct Element {
  308. not_null<DocumentData*> document;
  309. std::shared_ptr<Data::DocumentMedia> documentMedia;
  310. Lottie::Animation *lottie = nullptr;
  311. Media::Clip::ReaderPointer webm;
  312. Ui::Text::CustomEmoji *emoji = nullptr;
  313. Ui::Animations::Simple overAnimation;
  314. mutable QImage premiumLock;
  315. };
  316. void visibleTopBottomUpdated(int visibleTop, int visibleBottom) override;
  317. [[nodiscard]] Ui::MessageSendingAnimationFrom messageSentAnimationInfo(
  318. int index,
  319. not_null<DocumentData*> document) const;
  320. [[nodiscard]] QSize boundingBoxSize() const;
  321. void paintSticker(
  322. Painter &p,
  323. int index,
  324. QPoint position,
  325. bool paused,
  326. crl::time now) const;
  327. void shakeTransform(
  328. QPainter &p,
  329. int index,
  330. QPoint position,
  331. crl::time now) const;
  332. void setupLottie(int index);
  333. void setupWebm(int index);
  334. void clipCallback(
  335. Media::Clip::Notification notification,
  336. not_null<DocumentData*> document,
  337. int index);
  338. void setupEmoji(int index);
  339. [[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomEmoji(
  340. not_null<DocumentData*> document);
  341. void customEmojiRepaint();
  342. void updateSelected();
  343. void setSelected(int selected);
  344. void startOverAnimation(int index, float64 from, float64 to);
  345. int stickerFromGlobalPos(const QPoint &p) const;
  346. void installDone(const MTPmessages_StickerSetInstallResult &result);
  347. void requestReorder(not_null<DocumentData*> document, int index);
  348. void fillDeleteStickerBox(not_null<Ui::GenericBox*> box, int index);
  349. void chosen(
  350. int index,
  351. not_null<DocumentData*> sticker,
  352. Api::SendOptions options);
  353. [[nodiscard]] QPoint posFromIndex(int index) const;
  354. [[nodiscard]] bool isDraggedAnimating() const;
  355. not_null<Lottie::MultiPlayer*> getLottiePlayer();
  356. void showPreview();
  357. void showPreviewAt(QPoint globalPos);
  358. void updateItems();
  359. void repaintItems(crl::time now = 0);
  360. const std::shared_ptr<ChatHelpers::Show> _show;
  361. const not_null<Main::Session*> _session;
  362. MTP::Sender _api;
  363. std::vector<Element> _elements;
  364. std::unique_ptr<Lottie::MultiPlayer> _lottiePlayer;
  365. base::flat_map<
  366. not_null<DocumentData*>,
  367. std::unique_ptr<Ui::Text::CustomEmoji>> _customEmoji;
  368. bool _repaintScheduled = false;
  369. StickersPack _pack;
  370. base::flat_map<EmojiPtr, StickersPack> _emoji;
  371. bool _loaded = false;
  372. uint64 _setId = 0;
  373. uint64 _setAccessHash = 0;
  374. uint64 _setHash = 0;
  375. DocumentId _setThumbnailDocumentId = 0;
  376. QString _setTitle, _setShortName;
  377. int _setCount = 0;
  378. Data::StickersSetFlags _setFlags;
  379. int _rowsCount = 0;
  380. int _perRow = 0;
  381. QSize _singleSize;
  382. TimeId _setInstallDate = TimeId(0);
  383. StickerType _setThumbnailType = StickerType::Webp;
  384. ImageWithLocation _setThumbnail;
  385. bool _amSetCreator = false;
  386. struct {
  387. bool enabled = false;
  388. int index = -1;
  389. int lastSelected = -1;
  390. QPoint point;
  391. } _dragging;
  392. Ui::Animations::Basic _shakeAnimation;
  393. std::deque<Fn<void()>> _reorderRequests;
  394. std::optional<MTP::Sender> _apiReorder;
  395. struct ShiftAnimation final {
  396. Ui::Animations::Simple animation;
  397. Ui::Animations::Simple yAnimation;
  398. int shift = 0;
  399. };
  400. base::flat_map<int, ShiftAnimation> _shiftAnimations;
  401. const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
  402. mutable StickerPremiumMark _premiumMark;
  403. int _visibleTop = 0;
  404. int _visibleBottom = 0;
  405. crl::time _lastScrolledAt = 0;
  406. crl::time _lastUpdatedAt = 0;
  407. base::Timer _updateItemsTimer;
  408. StickerSetIdentifier _input;
  409. QMargins _padding;
  410. mtpRequestId _installRequest = 0;
  411. int _selected = -1;
  412. base::Timer _previewTimer;
  413. int _previewShown = -1;
  414. base::unique_qptr<Ui::PopupMenu> _menu;
  415. rpl::event_stream<uint64> _setInstalled;
  416. rpl::event_stream<uint64> _setArchived;
  417. rpl::event_stream<> _updateControls;
  418. rpl::event_stream<Error> _errors;
  419. };
  420. StickerSetBox::StickerSetBox(
  421. QWidget *parent,
  422. std::shared_ptr<ChatHelpers::Show> show,
  423. const StickerSetIdentifier &set,
  424. Data::StickersType type)
  425. : _show(std::move(show))
  426. , _session(&_show->session())
  427. , _set(set)
  428. , _type(type) {
  429. }
  430. StickerSetBox::StickerSetBox(
  431. QWidget *parent,
  432. std::shared_ptr<ChatHelpers::Show> show,
  433. not_null<Data::StickersSet*> set)
  434. : StickerSetBox(parent, std::move(show), set->identifier(), set->type()) {
  435. }
  436. QPointer<Ui::BoxContent> StickerSetBox::Show(
  437. std::shared_ptr<ChatHelpers::Show> show,
  438. not_null<DocumentData*> document) {
  439. if (const auto sticker = document->sticker()) {
  440. if (sticker->set) {
  441. auto box = Box<StickerSetBox>(
  442. show,
  443. sticker->set,
  444. sticker->setType);
  445. const auto result = QPointer<Ui::BoxContent>(box.data());
  446. show->showBox(std::move(box));
  447. return result;
  448. }
  449. }
  450. return nullptr;
  451. }
  452. void StickerSetBox::prepare() {
  453. setTitle(tr::lng_contacts_loading());
  454. _inner = setInnerWidget(
  455. object_ptr<Inner>(this, _show, _set, _type),
  456. st::stickersScroll);
  457. _session->data().stickers().updated(
  458. _type
  459. ) | rpl::start_with_next([=] {
  460. updateButtons();
  461. }, lifetime());
  462. setDimensions(
  463. st::boxWideWidth,
  464. (_type == Data::StickersType::Emoji
  465. ? st::emojiSetMaxHeight
  466. : st::stickersMaxHeight));
  467. updateTitleAndButtons();
  468. _inner->updateControls(
  469. ) | rpl::start_with_next([=] {
  470. updateTitleAndButtons();
  471. }, lifetime());
  472. _inner->setInstalled(
  473. ) | rpl::start_with_next([=](uint64 setId) {
  474. if (_inner->setType() == Data::StickersType::Masks) {
  475. showToast(tr::lng_masks_installed(tr::now));
  476. } else if (_inner->setType() == Data::StickersType::Emoji) {
  477. auto &stickers = _session->data().stickers();
  478. stickers.notifyEmojiSetInstalled(setId);
  479. } else if (_inner->setType() == Data::StickersType::Stickers) {
  480. auto &stickers = _session->data().stickers();
  481. stickers.notifyStickerSetInstalled(setId);
  482. }
  483. closeBox();
  484. }, lifetime());
  485. _inner->errors(
  486. ) | rpl::start_with_next([=](Error error) {
  487. handleError(error);
  488. }, lifetime());
  489. _inner->setArchived(
  490. ) | rpl::start_with_next([=](uint64 setId) {
  491. const auto type = _inner->setType();
  492. if (type == Data::StickersType::Emoji) {
  493. return;
  494. }
  495. showToast((type == Data::StickersType::Masks)
  496. ? tr::lng_masks_has_been_archived(tr::now)
  497. : tr::lng_stickers_has_been_archived(tr::now));
  498. auto &order = (type == Data::StickersType::Masks)
  499. ? _session->data().stickers().maskSetsOrderRef()
  500. : _session->data().stickers().setsOrderRef();
  501. const auto index = order.indexOf(setId);
  502. if (index != -1) {
  503. order.removeAt(index);
  504. auto &local = _session->local();
  505. if (type == Data::StickersType::Masks) {
  506. local.writeInstalledMasks();
  507. local.writeArchivedMasks();
  508. } else {
  509. local.writeInstalledStickers();
  510. local.writeArchivedStickers();
  511. }
  512. }
  513. _session->data().stickers().notifyUpdated(type);
  514. closeBox();
  515. }, lifetime());
  516. }
  517. void StickerSetBox::addStickers() {
  518. _inner->install();
  519. }
  520. void StickerSetBox::copyStickersLink() {
  521. const auto part = _inner->isEmojiSet() ? u"addemoji"_q : "addstickers";
  522. const auto url = _session->createInternalLinkFull(
  523. part + '/' + _inner->shortName());
  524. QGuiApplication::clipboard()->setText(url);
  525. }
  526. void StickerSetBox::handleError(Error error) {
  527. const auto guard = gsl::finally(crl::guard(this, [=] {
  528. closeBox();
  529. }));
  530. switch (error) {
  531. case Error::NotFound:
  532. _show->showBox(
  533. Ui::MakeInformBox(tr::lng_stickers_not_found(tr::now)));
  534. break;
  535. default: Unexpected("Error in StickerSetBox::handleError.");
  536. }
  537. }
  538. void StickerSetBox::updateTitleAndButtons() {
  539. setTitle(_inner->title());
  540. updateButtons();
  541. }
  542. void ChangeSetNameBox(
  543. not_null<Ui::GenericBox*> box,
  544. not_null<Data::Session*> data,
  545. const StickerSetIdentifier &input,
  546. Fn<void(TLStickerSet)> done) {
  547. struct State final {
  548. rpl::variable<mtpRequestId> requestId = 0;
  549. Ui::RpWidget* saveButton = nullptr;
  550. };
  551. box->setTitle(tr::lng_stickers_box_edit_name_title());
  552. box->addRow(
  553. object_ptr<Ui::FlatLabel>(
  554. box,
  555. tr::lng_stickers_box_edit_name_about(),
  556. st::boxLabel));
  557. const auto state = box->lifetime().make_state<State>();
  558. const auto wasName = [&] {
  559. const auto &sets = data->stickers().sets();
  560. const auto it = sets.find(input.id);
  561. return (it == sets.end()) ? QString() : it->second->title;
  562. }();
  563. const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
  564. box,
  565. st::editStickerSetNameField.heightMin));
  566. auto owned = object_ptr<Ui::InputField>(
  567. wrap,
  568. st::editStickerSetNameField,
  569. tr::lng_stickers_context_edit_name(),
  570. wasName);
  571. const auto field = owned.data();
  572. wrap->widthValue() | rpl::start_with_next([=](int width) {
  573. field->move(0, 0);
  574. field->resize(width, field->height());
  575. wrap->resize(width, field->height());
  576. }, wrap->lifetime());
  577. field->selectAll();
  578. constexpr auto kMaxSetNameLength = 50;
  579. field->setMaxLength(kMaxSetNameLength);
  580. Ui::AddLengthLimitLabel(field, kMaxSetNameLength, kMaxSetNameLength + 1);
  581. box->setFocusCallback([=] { field->setFocusFast(); });
  582. const auto close = crl::guard(box, [=] { box->closeBox(); });
  583. const auto save = [=, show = box->uiShow()] {
  584. if (state->requestId.current()) {
  585. return;
  586. }
  587. const auto text = field->getLastText().trimmed();
  588. if ((Ui::ComputeRealUnicodeCharactersCount(text) > kMaxSetNameLength)
  589. || text.isEmpty()) {
  590. field->showError();
  591. return;
  592. }
  593. const auto buttonWidth = state->saveButton
  594. ? state->saveButton->width()
  595. : 0;
  596. state->requestId = data->session().api().request(
  597. MTPstickers_RenameStickerSet(
  598. Data::InputStickerSet(input),
  599. MTP_string(text))
  600. ).done([=](const TLStickerSet &result) {
  601. result.match([&](const MTPDmessages_stickerSet &d) {
  602. data->stickers().feedSetFull(d);
  603. data->stickers().notifyUpdated(Data::StickersType::Stickers);
  604. }, [](const auto &) {
  605. });
  606. done(result);
  607. close();
  608. }).fail([=](const MTP::Error &error) {
  609. show->showToast(error.type());
  610. close();
  611. }).send();
  612. if (state->saveButton) {
  613. state->saveButton->resizeToWidth(buttonWidth);
  614. }
  615. };
  616. state->saveButton = box->addButton(
  617. rpl::conditional(
  618. state->requestId.value() | rpl::map(rpl::mappers::_1 > 0),
  619. rpl::single(QString()),
  620. tr::lng_box_done()),
  621. save);
  622. if (const auto saveButton = state->saveButton) {
  623. using namespace Info::Statistics;
  624. const auto loadingAnimation = InfiniteRadialAnimationWidget(
  625. saveButton,
  626. saveButton->height() / 2,
  627. &st::editStickerSetNameLoading);
  628. AddChildToWidgetCenter(saveButton, loadingAnimation);
  629. loadingAnimation->showOn(
  630. state->requestId.value() | rpl::map(rpl::mappers::_1 > 0));
  631. }
  632. box->addButton(tr::lng_cancel(), [=] {
  633. data->session().api().request(state->requestId.current()).cancel();
  634. close();
  635. });
  636. }
  637. void StickerSetBox::updateButtons() {
  638. clearButtons();
  639. if (_inner->reorderState()) {
  640. addButton(tr::lng_box_done(), [=] {
  641. _inner->setReorderState(false);
  642. updateButtons();
  643. });
  644. } else if (_inner->loaded()) {
  645. const auto type = _inner->setType();
  646. const auto share = [=] {
  647. copyStickersLink();
  648. showToast(type == Data::StickersType::Emoji
  649. ? tr::lng_stickers_copied_emoji(tr::now)
  650. : tr::lng_stickers_copied(tr::now));
  651. };
  652. const auto fillSetCreatorMenu = [&] {
  653. using Filler = Fn<void(not_null<Ui::PopupMenu*>)>;
  654. if (!_inner->amSetCreator()) {
  655. return Filler(nullptr);
  656. }
  657. const auto data = &_session->data();
  658. return Filler([=, show = _show, set = _set](
  659. not_null<Ui::PopupMenu*> menu) {
  660. const auto done = [inner = _inner](const TLStickerSet &set) {
  661. if (const auto raw = inner.data()) {
  662. raw->applySet(set);
  663. }
  664. };
  665. menu->addAction(
  666. tr::lng_stickers_context_edit_name(tr::now),
  667. [=] {
  668. show->showBox(Box(ChangeSetNameBox, data, set, done));
  669. },
  670. &st::menuIconEdit);
  671. menu->addAction(
  672. tr::lng_stickers_context_reorder(tr::now),
  673. [=] {
  674. _inner->setReorderState(true);
  675. updateButtons();
  676. },
  677. &st::menuIconManage);
  678. });
  679. }();
  680. if (_inner->notInstalled()) {
  681. if (!_session->premium()
  682. && _session->premiumPossible()
  683. && _inner->premiumEmojiSet()) {
  684. const auto &st = st::premiumPreviewDoubledLimitsBox;
  685. setStyle(st);
  686. auto button = CreateUnlockButton(
  687. this,
  688. tr::lng_premium_unlock_emoji());
  689. button->resizeToWidth(st::boxWideWidth
  690. - st.buttonPadding.left()
  691. - st.buttonPadding.left());
  692. button->setClickedCallback([=] {
  693. if (const auto window = _show->resolveWindow()) {
  694. Settings::ShowPremium(window, u"animated_emoji"_q);
  695. }
  696. });
  697. addButton(std::move(button));
  698. } else {
  699. auto addText = (type == Data::StickersType::Emoji)
  700. ? tr::lng_stickers_add_emoji()
  701. : (type == Data::StickersType::Masks)
  702. ? tr::lng_stickers_add_masks()
  703. : tr::lng_stickers_add_pack();
  704. addButton(std::move(addText), [=] { addStickers(); });
  705. addButton(tr::lng_cancel(), [=] { closeBox(); });
  706. }
  707. if (!_inner->shortName().isEmpty()) {
  708. const auto top = addTopButton(st::infoTopBarMenu);
  709. const auto menu
  710. = std::make_shared<base::unique_qptr<Ui::PopupMenu>>();
  711. top->setClickedCallback([=] {
  712. *menu = base::make_unique_q<Ui::PopupMenu>(
  713. top,
  714. st::popupMenuWithIcons);
  715. if (fillSetCreatorMenu) {
  716. fillSetCreatorMenu(*menu);
  717. }
  718. (*menu)->addAction(
  719. ((type == Data::StickersType::Emoji)
  720. ? tr::lng_stickers_share_emoji
  721. : (type == Data::StickersType::Masks)
  722. ? tr::lng_stickers_share_masks
  723. : tr::lng_stickers_share_pack)(tr::now),
  724. [=] { share(); closeBox(); },
  725. &st::menuIconShare);
  726. (*menu)->popup(QCursor::pos());
  727. return true;
  728. });
  729. }
  730. } else if (_inner->official()) {
  731. addButton(tr::lng_about_done(), [=] { closeBox(); });
  732. } else {
  733. auto shareText = (type == Data::StickersType::Emoji)
  734. ? tr::lng_stickers_share_emoji()
  735. : (type == Data::StickersType::Masks)
  736. ? tr::lng_stickers_share_masks()
  737. : tr::lng_stickers_share_pack();
  738. addButton(std::move(shareText), std::move(share));
  739. addButton(tr::lng_cancel(), [=] { closeBox(); });
  740. if (!_inner->shortName().isEmpty()) {
  741. const auto top = addTopButton(st::infoTopBarMenu);
  742. const auto archive = [=] {
  743. _inner->archiveStickers();
  744. };
  745. const auto remove = [=] {
  746. const auto session = &_show->session();
  747. auto box = ChatHelpers::MakeConfirmRemoveSetBox(
  748. session,
  749. st::boxLabel,
  750. _inner->setId());
  751. if (box) {
  752. _show->showBox(std::move(box));
  753. }
  754. };
  755. const auto menu
  756. = std::make_shared<base::unique_qptr<Ui::PopupMenu>>();
  757. top->setClickedCallback([=] {
  758. *menu = base::make_unique_q<Ui::PopupMenu>(
  759. top,
  760. st::popupMenuWithIcons);
  761. if (type == Data::StickersType::Emoji) {
  762. (*menu)->addAction(
  763. tr::lng_custom_emoji_remove_pack_button(tr::now),
  764. remove,
  765. &st::menuIconRemove);
  766. } else {
  767. if (fillSetCreatorMenu) {
  768. fillSetCreatorMenu(*menu);
  769. }
  770. (*menu)->addAction(
  771. (type == Data::StickersType::Masks
  772. ? tr::lng_masks_archive_pack(tr::now)
  773. : tr::lng_stickers_archive_pack(tr::now)),
  774. archive,
  775. &st::menuIconArchive);
  776. }
  777. (*menu)->popup(QCursor::pos());
  778. return true;
  779. });
  780. }
  781. }
  782. } else {
  783. addButton(tr::lng_cancel(), [=] { closeBox(); });
  784. }
  785. update();
  786. }
  787. void StickerSetBox::resizeEvent(QResizeEvent *e) {
  788. BoxContent::resizeEvent(e);
  789. _inner->resize(width(), _inner->height());
  790. }
  791. StickerSetBox::Inner::Inner(
  792. QWidget *parent,
  793. std::shared_ptr<ChatHelpers::Show> show,
  794. const StickerSetIdentifier &set,
  795. Data::StickersType type)
  796. : RpWidget(parent)
  797. , _show(std::move(show))
  798. , _session(&_show->session())
  799. , _api(&_session->mtp())
  800. , _setId(set.id)
  801. , _setAccessHash(set.accessHash)
  802. , _setShortName(set.shortName)
  803. , _pathGradient(std::make_unique<Ui::PathShiftGradient>(
  804. st::windowBgRipple,
  805. st::windowBgOver,
  806. [=] { repaintItems(); }))
  807. , _premiumMark(_session, st::stickersPremiumLock)
  808. , _updateItemsTimer([=] { updateItems(); })
  809. , _input(set)
  810. , _padding((type == Data::StickersType::Emoji)
  811. ? st::emojiSetPadding
  812. : st::stickersPadding)
  813. , _previewTimer([=] { showPreview(); }) {
  814. setAttribute(Qt::WA_OpaquePaintEvent);
  815. _api.request(MTPmessages_GetStickerSet(
  816. Data::InputStickerSet(_input),
  817. MTP_int(0) // hash
  818. )).done([=](const TLStickerSet &result) {
  819. applySet(result);
  820. }).fail([=] {
  821. _loaded = true;
  822. _errors.fire(Error::NotFound);
  823. }).send();
  824. _session->api().updateStickers();
  825. _session->downloaderTaskFinished(
  826. ) | rpl::start_with_next([=] {
  827. updateItems();
  828. }, lifetime());
  829. setMouseTracking(true);
  830. }
  831. void StickerSetBox::Inner::applySet(const TLStickerSet &set) {
  832. _pack.clear();
  833. _emoji.clear();
  834. _elements.clear();
  835. _selected = -1;
  836. setCursor(style::cur_default);
  837. const auto owner = &_session->data();
  838. const auto premiumPossible = _session->premiumPossible();
  839. set.match([&](const MTPDmessages_stickerSet &data) {
  840. const auto &v = data.vdocuments().v;
  841. _pack.reserve(v.size());
  842. _elements.reserve(v.size());
  843. for (const auto &item : v) {
  844. const auto document = owner->processDocument(item);
  845. const auto sticker = document->sticker();
  846. if (!sticker) {
  847. continue;
  848. }
  849. _pack.push_back(document);
  850. if (!document->isPremiumSticker() || premiumPossible) {
  851. _elements.push_back({
  852. document,
  853. document->createMediaView(),
  854. });
  855. }
  856. }
  857. for (const auto &pack : data.vpacks().v) {
  858. pack.match([&](const MTPDstickerPack &pack) {
  859. if (const auto emoji = Ui::Emoji::Find(qs(pack.vemoticon()))) {
  860. const auto original = emoji->original();
  861. auto &stickers = pack.vdocuments().v;
  862. auto p = StickersPack();
  863. p.reserve(stickers.size());
  864. for (auto j = 0, c = int(stickers.size()); j != c; ++j) {
  865. auto doc = _session->data().document(stickers[j].v);
  866. if (!doc || !doc->sticker()) continue;
  867. p.push_back(doc);
  868. }
  869. _emoji[original] = std::move(p);
  870. }
  871. });
  872. }
  873. {
  874. const auto &set = data.vset().data();
  875. _setTitle = _session->data().stickers().getSetTitle(
  876. set);
  877. _setShortName = qs(set.vshort_name());
  878. _setId = set.vid().v;
  879. _setAccessHash = set.vaccess_hash().v;
  880. _setHash = set.vhash().v;
  881. _setCount = set.vcount().v;
  882. _setFlags = Data::ParseStickersSetFlags(set);
  883. _setInstallDate = set.vinstalled_date().value_or(0);
  884. _setThumbnailDocumentId = set.vthumb_document_id().value_or_empty();
  885. _amSetCreator = set.is_creator();
  886. _setThumbnail = [&] {
  887. if (const auto thumbs = set.vthumbs()) {
  888. for (const auto &thumb : thumbs->v) {
  889. const auto result = Images::FromPhotoSize(
  890. _session,
  891. set,
  892. thumb);
  893. if (result.location.valid()) {
  894. _setThumbnailType
  895. = Data::ThumbnailTypeFromPhotoSize(thumb);
  896. return result;
  897. }
  898. }
  899. }
  900. return ImageWithLocation();
  901. }();
  902. const auto &sets = _session->data().stickers().sets();
  903. const auto it = sets.find(_setId);
  904. if (it != sets.cend()) {
  905. const auto set = it->second.get();
  906. const auto clientFlags = set->flags
  907. & (SetFlag::Featured
  908. | SetFlag::NotLoaded
  909. | SetFlag::Unread
  910. | SetFlag::Special);
  911. _setFlags |= clientFlags;
  912. set->flags = _setFlags;
  913. set->installDate = _setInstallDate;
  914. set->stickers = _pack;
  915. set->emoji = _emoji;
  916. set->setThumbnail(_setThumbnail, _setThumbnailType);
  917. }
  918. };
  919. }, [&](const MTPDmessages_stickerSetNotModified &data) {
  920. LOG(("API Error: Unexpected messages.stickerSetNotModified."));
  921. });
  922. if (_pack.isEmpty()) {
  923. _errors.fire(Error::NotFound);
  924. return;
  925. }
  926. _perRow = isEmojiSet() ? kEmojiPerRow : kStickersPerRow;
  927. _rowsCount = (_pack.size() + _perRow - 1) / _perRow;
  928. _singleSize = isEmojiSet() ? st::emojiSetSize : st::stickersSize;
  929. resize(
  930. _padding.left() + _perRow * _singleSize.width(),
  931. _padding.top() + _rowsCount * _singleSize.height() + _padding.bottom());
  932. _loaded = true;
  933. updateSelected();
  934. _updateControls.fire({});
  935. }
  936. rpl::producer<uint64> StickerSetBox::Inner::setInstalled() const {
  937. return _setInstalled.events();
  938. }
  939. rpl::producer<uint64> StickerSetBox::Inner::setArchived() const {
  940. return _setArchived.events();
  941. }
  942. rpl::producer<> StickerSetBox::Inner::updateControls() const {
  943. return _updateControls.events();
  944. }
  945. rpl::producer<StickerSetBox::Error> StickerSetBox::Inner::errors() const {
  946. return _errors.events();
  947. }
  948. void StickerSetBox::Inner::installDone(
  949. const MTPmessages_StickerSetInstallResult &result) {
  950. auto &stickers = _session->data().stickers();
  951. auto &sets = stickers.setsRef();
  952. const auto type = setType();
  953. const bool wasArchived = (_setFlags & SetFlag::Archived);
  954. if (wasArchived && type != Data::StickersType::Emoji) {
  955. const auto index = ((type == Data::StickersType::Masks)
  956. ? stickers.archivedMaskSetsOrderRef()
  957. : stickers.archivedSetsOrderRef()).indexOf(_setId);
  958. if (index >= 0) {
  959. ((type == Data::StickersType::Masks)
  960. ? stickers.archivedMaskSetsOrderRef()
  961. : stickers.archivedSetsOrderRef()).removeAt(index);
  962. }
  963. }
  964. _setInstallDate = base::unixtime::now();
  965. _setFlags &= ~SetFlag::Archived;
  966. _setFlags |= SetFlag::Installed;
  967. auto it = sets.find(_setId);
  968. if (it == sets.cend()) {
  969. it = sets.emplace(
  970. _setId,
  971. std::make_unique<StickersSet>(
  972. &_session->data(),
  973. _setId,
  974. _setAccessHash,
  975. _setHash,
  976. _setTitle,
  977. _setShortName,
  978. _setCount,
  979. _setFlags,
  980. _setInstallDate)).first;
  981. } else {
  982. it->second->flags = _setFlags;
  983. it->second->installDate = _setInstallDate;
  984. }
  985. const auto set = it->second.get();
  986. set->thumbnailDocumentId = _setThumbnailDocumentId;
  987. set->setThumbnail(_setThumbnail, _setThumbnailType);
  988. set->stickers = _pack;
  989. set->emoji = _emoji;
  990. auto &order = (type == Data::StickersType::Emoji)
  991. ? stickers.emojiSetsOrderRef()
  992. : (type == Data::StickersType::Masks)
  993. ? stickers.maskSetsOrderRef()
  994. : stickers.setsOrderRef();
  995. const auto insertAtIndex = 0, currentIndex = int(order.indexOf(_setId));
  996. if (currentIndex != insertAtIndex) {
  997. if (currentIndex > 0) {
  998. order.removeAt(currentIndex);
  999. }
  1000. order.insert(insertAtIndex, _setId);
  1001. }
  1002. const auto customIt = sets.find(Data::Stickers::CustomSetId);
  1003. if (customIt != sets.cend()) {
  1004. const auto custom = customIt->second.get();
  1005. for (const auto sticker : std::as_const(_pack)) {
  1006. const int removeIndex = custom->stickers.indexOf(sticker);
  1007. if (removeIndex >= 0) {
  1008. custom->stickers.removeAt(removeIndex);
  1009. }
  1010. }
  1011. if (custom->stickers.isEmpty()) {
  1012. sets.erase(customIt);
  1013. }
  1014. }
  1015. if (result.type() == mtpc_messages_stickerSetInstallResultArchive) {
  1016. stickers.applyArchivedResult(
  1017. result.c_messages_stickerSetInstallResultArchive());
  1018. } else {
  1019. auto &storage = _session->local();
  1020. if (wasArchived && type != Data::StickersType::Emoji) {
  1021. if (type == Data::StickersType::Masks) {
  1022. storage.writeArchivedMasks();
  1023. } else {
  1024. storage.writeArchivedStickers();
  1025. }
  1026. }
  1027. if (type == Data::StickersType::Emoji) {
  1028. storage.writeInstalledCustomEmoji();
  1029. } else if (type == Data::StickersType::Masks) {
  1030. storage.writeInstalledMasks();
  1031. } else {
  1032. storage.writeInstalledStickers();
  1033. }
  1034. stickers.notifyUpdated(type);
  1035. }
  1036. _setInstalled.fire_copy(_setId);
  1037. }
  1038. void StickerSetBox::Inner::mousePressEvent(QMouseEvent *e) {
  1039. if (e->button() != Qt::LeftButton) {
  1040. return;
  1041. }
  1042. const auto index = stickerFromGlobalPos(e->globalPos());
  1043. if (index < 0 || index >= _pack.size()) {
  1044. return;
  1045. }
  1046. if (_dragging.enabled) {
  1047. _previewTimer.cancel();
  1048. if (isDraggedAnimating()) {
  1049. return;
  1050. }
  1051. _dragging.index = index;
  1052. _dragging.point = mapFromGlobal(QCursor::pos()) - posFromIndex(index);
  1053. return;
  1054. }
  1055. _previewTimer.callOnce(QApplication::startDragTime());
  1056. }
  1057. void StickerSetBox::Inner::mouseMoveEvent(QMouseEvent *e) {
  1058. updateSelected();
  1059. const auto draggedAnimating = isDraggedAnimating();
  1060. if (_selected >= 0 && !draggedAnimating) {
  1061. _dragging.lastSelected = _selected;
  1062. }
  1063. if (_dragging.index >= 0
  1064. && _dragging.index < _pack.size()
  1065. && _dragging.lastSelected >= 0
  1066. && !draggedAnimating) {
  1067. for (auto i = 0; i < _pack.size(); i++) {
  1068. if (i == _dragging.index) {
  1069. continue;
  1070. }
  1071. auto &entry = _shiftAnimations[i];
  1072. const auto wasShift = entry.shift;
  1073. if ((i >= _dragging.index) && (i <= _dragging.lastSelected)) {
  1074. if (entry.shift == 0) {
  1075. entry.shift = -1;
  1076. } else if (entry.shift == 1) {
  1077. entry.shift = 0;
  1078. }
  1079. } else if ((i < _dragging.index)
  1080. && (i >= _dragging.lastSelected)) {
  1081. if (entry.shift == 0) {
  1082. entry.shift = 1;
  1083. } else if (entry.shift == -1) {
  1084. entry.shift = 0;
  1085. }
  1086. }
  1087. if ((i < std::min(_dragging.index, _dragging.lastSelected))
  1088. || (i > std::max(_dragging.index, _dragging.lastSelected))) {
  1089. entry.shift = 0;
  1090. }
  1091. if (wasShift != entry.shift) {
  1092. const auto fromPoint = posFromIndex(i + wasShift);
  1093. const auto toPoint = posFromIndex(i + entry.shift);
  1094. const auto toX = float64(toPoint.x());
  1095. const auto toY = float64(toPoint.y());
  1096. const auto ratio = [&] {
  1097. const auto fromX = entry.animation.value(toX);
  1098. const auto ratioX = std::min(toX, fromX)
  1099. / std::max(toX, fromX);
  1100. const auto fromY = entry.yAnimation.value(toY);
  1101. const auto ratioY = std::min(toY, fromY)
  1102. / std::max(toY, fromY);
  1103. return (ratioX == 1.)
  1104. ? ratioY
  1105. : (ratioY == 1.)
  1106. ? ratioX
  1107. : std::max(ratioX, ratioY);
  1108. }();
  1109. if (!entry.animation.animating()) {
  1110. entry.animation.stop();
  1111. entry.animation.start(
  1112. [=] { update(); },
  1113. fromPoint.x(),
  1114. toX,
  1115. kStickerMoveDuration);
  1116. } else {
  1117. entry.animation.change(
  1118. toX,
  1119. kStickerMoveDuration * (1. - ratio),
  1120. anim::linear);
  1121. }
  1122. if (!entry.yAnimation.animating()) {
  1123. entry.yAnimation.stop();
  1124. entry.yAnimation.start(
  1125. [=] { update(); },
  1126. fromPoint.y(),
  1127. toY,
  1128. kStickerMoveDuration);
  1129. } else {
  1130. entry.yAnimation.change(
  1131. toY,
  1132. kStickerMoveDuration * (1. - ratio),
  1133. anim::linear);
  1134. }
  1135. }
  1136. }
  1137. update();
  1138. }
  1139. if (_previewShown >= 0) {
  1140. showPreviewAt(e->globalPos());
  1141. }
  1142. }
  1143. void StickerSetBox::Inner::showPreviewAt(QPoint globalPos) {
  1144. const auto index = stickerFromGlobalPos(globalPos);
  1145. if (index >= 0
  1146. && index < _pack.size()
  1147. && index != _previewShown) {
  1148. _previewShown = index;
  1149. _show->showMediaPreview(
  1150. Data::FileOriginStickerSet(_setId, _setAccessHash),
  1151. _pack[_previewShown]);
  1152. }
  1153. }
  1154. void StickerSetBox::Inner::leaveEventHook(QEvent *e) {
  1155. setSelected(-1);
  1156. }
  1157. void StickerSetBox::Inner::requestReorder(
  1158. not_null<DocumentData*> document,
  1159. int index) {
  1160. if (!_apiReorder) {
  1161. _apiReorder.emplace(&_session->mtp());
  1162. }
  1163. _reorderRequests.emplace_back([document, index, this] {
  1164. _apiReorder->request(
  1165. MTPstickers_ChangeStickerPosition(
  1166. document->mtpInput(),
  1167. MTP_int(index))
  1168. ).done([this, document](const TLStickerSet &result) {
  1169. result.match([&](const MTPDmessages_stickerSet &d) {
  1170. document->owner().stickers().feedSetFull(d);
  1171. document->owner().stickers().notifyUpdated(
  1172. Data::StickersType::Stickers);
  1173. }, [](const auto &) {
  1174. });
  1175. if (!_reorderRequests.empty()) {
  1176. _reorderRequests.pop_front();
  1177. }
  1178. if (_reorderRequests.empty()) {
  1179. // applySet(result); // Causes stickers blink.
  1180. } else {
  1181. _reorderRequests.front()();
  1182. }
  1183. }).fail([show = _show](const MTP::Error &error) {
  1184. show->showToast(error.type());
  1185. }).send();
  1186. });
  1187. if (_reorderRequests.size() == 1) {
  1188. _reorderRequests.front()();
  1189. }
  1190. }
  1191. void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
  1192. if (_dragging.index >= 0 && !isDraggedAnimating()) {
  1193. const auto fromPos = mapFromGlobal(e->globalPos()) - _dragging.point;
  1194. const auto toPos = posFromIndex(_dragging.lastSelected);
  1195. const auto document = _pack[_dragging.index];
  1196. const auto wasPosition = _dragging.index;
  1197. const auto nowPosition = _dragging.lastSelected;
  1198. const auto finish = [=, this] {
  1199. requestReorder(document, nowPosition);
  1200. base::reorder(_pack, wasPosition, nowPosition);
  1201. base::reorder(_elements, wasPosition, nowPosition);
  1202. _dragging = {};
  1203. _dragging.enabled = true;
  1204. _shiftAnimations.clear();
  1205. };
  1206. auto &entry = _shiftAnimations[_dragging.index];
  1207. entry.animation.stop();
  1208. entry.yAnimation.stop();
  1209. entry.animation.start(
  1210. [finish, toPos, this](float64 value) {
  1211. const auto index = _dragging.index;
  1212. if (value >= toPos.x()
  1213. && index >= 0
  1214. && !_shiftAnimations[index].yAnimation.animating()) {
  1215. finish();
  1216. }
  1217. update();
  1218. },
  1219. fromPos.x(),
  1220. toPos.x(),
  1221. kStickerMoveDuration);
  1222. entry.yAnimation.start(
  1223. [finish, toPos, this](float64 value) {
  1224. const auto index = _dragging.index;
  1225. if (value >= toPos.y()
  1226. && index >= 0
  1227. && !_shiftAnimations[index].animation.animating()) {
  1228. finish();
  1229. }
  1230. update();
  1231. },
  1232. fromPos.y(),
  1233. toPos.y(),
  1234. kStickerMoveDuration);
  1235. }
  1236. if (_previewShown >= 0) {
  1237. _previewShown = -1;
  1238. return;
  1239. }
  1240. if (!_previewTimer.isActive()) {
  1241. return;
  1242. }
  1243. _previewTimer.cancel();
  1244. const auto index = stickerFromGlobalPos(e->globalPos());
  1245. if (index < 0 || index >= _pack.size()) {
  1246. return;
  1247. }
  1248. chosen(index, _pack[index], {});
  1249. }
  1250. void StickerSetBox::Inner::chosen(
  1251. int index,
  1252. not_null<DocumentData*> sticker,
  1253. Api::SendOptions options) {
  1254. const auto animation = options.scheduled
  1255. ? Ui::MessageSendingAnimationFrom()
  1256. : messageSentAnimationInfo(index, sticker);
  1257. _show->processChosenSticker({
  1258. .document = sticker,
  1259. .options = options,
  1260. .messageSendingFrom = animation,
  1261. });
  1262. }
  1263. auto StickerSetBox::Inner::messageSentAnimationInfo(
  1264. int index,
  1265. not_null<DocumentData*> document) const
  1266. -> Ui::MessageSendingAnimationFrom {
  1267. if (index < 0 || index >= _pack.size() || _pack[index] != document) {
  1268. return {};
  1269. }
  1270. const auto row = index / _perRow;
  1271. const auto column = index % _perRow;
  1272. const auto left = _padding.left() + column * _singleSize.width();
  1273. const auto top = _padding.top() + row * _singleSize.height();
  1274. const auto rect = QRect(QPoint(left, top), _singleSize);
  1275. const auto size = ChatHelpers::ComputeStickerSize(
  1276. document,
  1277. boundingBoxSize());
  1278. const auto innerPos = QPoint(
  1279. (rect.width() - size.width()) / 2,
  1280. (rect.height() - size.height()) / 2);
  1281. return {
  1282. .type = Ui::MessageSendingAnimationFrom::Type::Sticker,
  1283. .localId = _session->data().nextLocalMessageId(),
  1284. .globalStartGeometry = mapToGlobal(
  1285. QRect(rect.topLeft() + innerPos, size)),
  1286. };
  1287. }
  1288. void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
  1289. const auto index = stickerFromGlobalPos(e->globalPos());
  1290. if (index < 0
  1291. || index >= _pack.size()
  1292. || setType() == Data::StickersType::Masks) {
  1293. return;
  1294. }
  1295. _previewTimer.cancel();
  1296. _menu = base::make_unique_q<Ui::PopupMenu>(
  1297. this,
  1298. st::popupMenuWithIcons);
  1299. const auto details = _show->sendMenuDetails();
  1300. if (setType() == Data::StickersType::Emoji) {
  1301. if (const auto t = PrepareTextFromEmoji(_pack[index]); !t.empty()) {
  1302. _menu->addAction(tr::lng_mediaview_copy(tr::now), [=] {
  1303. if (auto data = TextUtilities::MimeDataFromText(t)) {
  1304. QGuiApplication::clipboard()->setMimeData(data.release());
  1305. }
  1306. }, &st::menuIconCopy);
  1307. }
  1308. } else if (details.type != SendMenu::Type::Disabled) {
  1309. const auto document = _pack[index];
  1310. const auto send = crl::guard(this, [=](Api::SendOptions options) {
  1311. chosen(index, document, options);
  1312. });
  1313. // In case we're adding items after FillSendMenu we have
  1314. // to pass nullptr for showForEffect and attach selector later.
  1315. // Otherwise added items widths won't be respected in menu geometry.
  1316. SendMenu::FillSendMenu(
  1317. _menu.get(),
  1318. nullptr, // showForEffect
  1319. details,
  1320. SendMenu::DefaultCallback(_show, send));
  1321. const auto show = _show;
  1322. const auto toggleFavedSticker = [=] {
  1323. Api::ToggleFavedSticker(
  1324. show,
  1325. document,
  1326. Data::FileOriginStickerSet(Data::Stickers::FavedSetId, 0));
  1327. };
  1328. const auto isFaved = document->owner().stickers().isFaved(document);
  1329. _menu->addAction(
  1330. (isFaved
  1331. ? tr::lng_faved_stickers_remove
  1332. : tr::lng_faved_stickers_add)(tr::now),
  1333. toggleFavedSticker,
  1334. (isFaved
  1335. ? &st::menuIconUnfave
  1336. : &st::menuIconFave));
  1337. if (amSetCreator()) {
  1338. const auto addAction = Ui::Menu::CreateAddActionCallback(
  1339. _menu.get());
  1340. addAction({
  1341. .text = tr::lng_stickers_context_delete(tr::now),
  1342. .handler = [index, this, show = _show] {
  1343. show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
  1344. fillDeleteStickerBox(box, index);
  1345. }));
  1346. },
  1347. .icon = &st::menuIconDeleteAttention,
  1348. .isAttention = true,
  1349. });
  1350. }
  1351. SendMenu::AttachSendMenuEffect(
  1352. _menu.get(),
  1353. _show,
  1354. details,
  1355. SendMenu::DefaultCallback(_show, send));
  1356. }
  1357. if (_menu->empty()) {
  1358. _menu = nullptr;
  1359. } else {
  1360. _menu->popup(QCursor::pos());
  1361. }
  1362. }
  1363. void StickerSetBox::Inner::fillDeleteStickerBox(
  1364. not_null<Ui::GenericBox*> box,
  1365. int index) {
  1366. Expects(index >= 0 || index < _pack.size());
  1367. const auto document = _pack[index];
  1368. const auto weak = Ui::MakeWeak(this);
  1369. const auto show = _show;
  1370. const auto container = box->verticalLayout();
  1371. Ui::AddSkip(container);
  1372. Ui::AddSkip(container);
  1373. const auto line = container->add(object_ptr<Ui::RpWidget>(container));
  1374. line->resize(line->width(), _singleSize.height());
  1375. const auto sticker = Ui::CreateChild<Ui::RpWidget>(line);
  1376. auto &lifetime = sticker->lifetime();
  1377. struct State final {
  1378. rpl::variable<mtpRequestId> requestId = 0;
  1379. Ui::RpWidget* saveButton = nullptr;
  1380. };
  1381. const auto state = lifetime.make_state<State>();
  1382. sticker->resize(_singleSize);
  1383. {
  1384. const auto animation = lifetime.make_state<Ui::Animations::Basic>();
  1385. animation->init([=] { sticker->update(); });
  1386. animation->start();
  1387. }
  1388. sticker->paintRequest(
  1389. ) | rpl::start_with_next([=] {
  1390. auto p = Painter(sticker);
  1391. if (const auto strong = weak.data()) {
  1392. const auto paused = On(PowerSaving::kStickersPanel)
  1393. || show->paused(ChatHelpers::PauseReason::Layer);
  1394. paintSticker(p, index, QPoint(), paused, crl::now());
  1395. if (_lottiePlayer && !paused) {
  1396. _lottiePlayer->markFrameShown();
  1397. }
  1398. }
  1399. }, sticker->lifetime());
  1400. const auto label = Ui::CreateChild<Ui::FlatLabel>(
  1401. line,
  1402. tr::lng_stickers_context_delete(),
  1403. box->getDelegate()->style().title);
  1404. line->widthValue(
  1405. ) | rpl::start_with_next([=](int width) {
  1406. sticker->moveToLeft(st::boxRowPadding.left(), 0);
  1407. const auto skip = st::defaultBoxCheckbox.textPosition.x();
  1408. label->resizeToWidth(width
  1409. - rect::right(sticker)
  1410. - skip
  1411. - st::boxRowPadding.right());
  1412. label->moveToLeft(
  1413. rect::right(sticker) + skip,
  1414. ((sticker->height() - label->height()) / 2));
  1415. }, label->lifetime());
  1416. sticker->setAttribute(Qt::WA_TransparentForMouseEvents);
  1417. label->setAttribute(Qt::WA_TransparentForMouseEvents);
  1418. Ui::AddSkip(container);
  1419. Ui::AddSkip(container);
  1420. box->addRow(
  1421. object_ptr<Ui::FlatLabel>(
  1422. container,
  1423. tr::lng_stickers_context_delete_sure(),
  1424. st::boxLabel));
  1425. const auto save = [=] {
  1426. if (state->requestId.current()) {
  1427. return;
  1428. }
  1429. const auto weakBox = Ui::MakeWeak(box);
  1430. const auto buttonWidth = state->saveButton
  1431. ? state->saveButton->width()
  1432. : 0;
  1433. state->requestId = document->owner().session().api().request(
  1434. MTPstickers_RemoveStickerFromSet(document->mtpInput()
  1435. )).done([=](const TLStickerSet &result) {
  1436. result.match([&](const MTPDmessages_stickerSet &d) {
  1437. document->owner().stickers().feedSetFull(d);
  1438. document->owner().stickers().notifyUpdated(
  1439. Data::StickersType::Stickers);
  1440. }, [](const auto &) {
  1441. });
  1442. if (const auto strong = weak.data()) {
  1443. applySet(result);
  1444. }
  1445. if (const auto strongBox = weakBox.data()) {
  1446. strongBox->closeBox();
  1447. }
  1448. }).fail([=](const MTP::Error &error) {
  1449. if (const auto strongBox = weakBox.data()) {
  1450. strongBox->uiShow()->showToast(error.type());
  1451. }
  1452. }).send();
  1453. if (state->saveButton) {
  1454. state->saveButton->resizeToWidth(buttonWidth);
  1455. }
  1456. };
  1457. state->saveButton = box->addButton(
  1458. rpl::conditional(
  1459. state->requestId.value() | rpl::map(rpl::mappers::_1 > 0),
  1460. rpl::single(QString()),
  1461. tr::lng_selected_delete()),
  1462. save,
  1463. st::attentionBoxButton);
  1464. if (const auto saveButton = state->saveButton) {
  1465. using namespace Info::Statistics;
  1466. const auto loadingAnimation = InfiniteRadialAnimationWidget(
  1467. saveButton,
  1468. saveButton->height() / 2,
  1469. &st::editStickerSetNameLoading);
  1470. AddChildToWidgetCenter(saveButton, loadingAnimation);
  1471. loadingAnimation->showOn(
  1472. state->requestId.value() | rpl::map(rpl::mappers::_1 > 0));
  1473. }
  1474. box->addButton(tr::lng_close(), [=] {
  1475. document->owner().session().api().request(
  1476. state->requestId.current()).cancel();
  1477. box->closeBox();
  1478. });
  1479. }
  1480. void StickerSetBox::Inner::updateSelected() {
  1481. auto selected = stickerFromGlobalPos(QCursor::pos());
  1482. setSelected(setType() == Data::StickersType::Masks ? -1 : selected);
  1483. }
  1484. void StickerSetBox::Inner::setSelected(int selected) {
  1485. if (_selected != selected) {
  1486. startOverAnimation(_selected, 1., 0.);
  1487. _selected = selected;
  1488. startOverAnimation(_selected, 0., 1.);
  1489. setCursor((_selected < 0)
  1490. ? style::cur_default
  1491. : _dragging.enabled
  1492. ? style::cur_sizeall
  1493. : style::cur_pointer);
  1494. }
  1495. }
  1496. void StickerSetBox::Inner::startOverAnimation(int index, float64 from, float64 to) {
  1497. if (index < 0 || index >= _elements.size()) {
  1498. return;
  1499. }
  1500. _elements[index].overAnimation.start([=] {
  1501. const auto row = index / _perRow;
  1502. const auto column = index % _perRow;
  1503. const auto left = _padding.left() + column * _singleSize.width();
  1504. const auto top = _padding.top() + row * _singleSize.height();
  1505. rtlupdate(left, top, _singleSize.width(), _singleSize.height());
  1506. }, from, to, st::emojiPanDuration);
  1507. }
  1508. void StickerSetBox::Inner::showPreview() {
  1509. _previewShown = -1;
  1510. showPreviewAt(QCursor::pos());
  1511. }
  1512. QPoint StickerSetBox::Inner::posFromIndex(int index) const {
  1513. return {
  1514. _padding.left() + (index % _perRow) * _singleSize.width(),
  1515. _padding.top() + (index / _perRow) * _singleSize.height(),
  1516. };
  1517. }
  1518. bool StickerSetBox::Inner::isDraggedAnimating() const {
  1519. if (_dragging.index < 0) {
  1520. return false;
  1521. }
  1522. const auto it = _shiftAnimations.find(_dragging.index);
  1523. return (it == _shiftAnimations.end())
  1524. ? false
  1525. : (it->second.animation.animating()
  1526. || it->second.yAnimation.animating());
  1527. }
  1528. not_null<Lottie::MultiPlayer*> StickerSetBox::Inner::getLottiePlayer() {
  1529. if (!_lottiePlayer) {
  1530. _lottiePlayer = std::make_unique<Lottie::MultiPlayer>(
  1531. Lottie::Quality::Default,
  1532. Lottie::MakeFrameRenderer());
  1533. _lottiePlayer->updates(
  1534. ) | rpl::start_with_next([=] {
  1535. updateItems();
  1536. }, lifetime());
  1537. }
  1538. return _lottiePlayer.get();
  1539. }
  1540. int32 StickerSetBox::Inner::stickerFromGlobalPos(const QPoint &p) const {
  1541. QPoint l(mapFromGlobal(p));
  1542. if (rtl()) l.setX(width() - l.x());
  1543. int32 row = (l.y() >= _padding.top()) ? qFloor((l.y() - _padding.top()) / _singleSize.height()) : -1;
  1544. int32 col = (l.x() >= _padding.left()) ? qFloor((l.x() - _padding.left()) / _singleSize.width()) : -1;
  1545. if (row >= 0 && col >= 0 && col < _perRow) {
  1546. int32 result = row * _perRow + col;
  1547. return (result < _pack.size()) ? result : -1;
  1548. }
  1549. return -1;
  1550. }
  1551. void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
  1552. Painter p(this);
  1553. _repaintScheduled = false;
  1554. p.fillRect(e->rect(), st::boxBg);
  1555. if (_elements.empty()) {
  1556. return;
  1557. }
  1558. int32 from = qFloor(e->rect().top() / _singleSize.height()), to = qFloor(e->rect().bottom() / _singleSize.height()) + 1;
  1559. _pathGradient->startFrame(0, width(), width() / 2);
  1560. const auto indexUnderCursor = (_dragging.index >= 0
  1561. && _dragging.index < _elements.size())
  1562. ? stickerFromGlobalPos(QCursor::pos())
  1563. : -2;
  1564. const auto lastIndex = indexUnderCursor >= 0
  1565. ? indexUnderCursor
  1566. : _dragging.lastSelected;
  1567. const auto now = crl::now();
  1568. const auto paused = On(PowerSaving::kStickersPanel)
  1569. || _show->paused(ChatHelpers::PauseReason::Layer);
  1570. for (int32 i = from; i < to; ++i) {
  1571. for (int32 j = 0; j < _perRow; ++j) {
  1572. int32 index = i * _perRow + j;
  1573. if (lastIndex >= 0) {
  1574. if (_dragging.index == index) {
  1575. continue;
  1576. }
  1577. const auto it = _shiftAnimations.find(index);
  1578. if (it != _shiftAnimations.end()) {
  1579. const auto &entry = it->second;
  1580. const auto toPos = posFromIndex(index + entry.shift);
  1581. const auto pos = QPoint(
  1582. entry.animation.value(toPos.x()),
  1583. entry.yAnimation.value(toPos.y()));
  1584. paintSticker(p, index, pos, paused, now);
  1585. continue;
  1586. }
  1587. }
  1588. if (index >= _elements.size()) {
  1589. break;
  1590. }
  1591. const auto pos = QPoint(
  1592. _padding.left() + j * _singleSize.width(),
  1593. _padding.top() + i * _singleSize.height());
  1594. paintSticker(p, index, pos, paused, now);
  1595. }
  1596. }
  1597. if (_dragging.index >= 0 && _dragging.index < _elements.size()) {
  1598. const auto pos = isDraggedAnimating()
  1599. ? QPoint(
  1600. _shiftAnimations[_dragging.index].animation.value(0),
  1601. _shiftAnimations[_dragging.index].yAnimation.value(0))
  1602. : (mapFromGlobal(QCursor::pos()) - _dragging.point);
  1603. paintSticker(p, _dragging.index, pos, paused, now);
  1604. }
  1605. if (_lottiePlayer && !paused) {
  1606. _lottiePlayer->markFrameShown();
  1607. }
  1608. }
  1609. bool StickerSetBox::Inner::isEmojiSet() const {
  1610. return (_setFlags & Data::StickersSetFlag::Emoji);
  1611. }
  1612. uint64 StickerSetBox::Inner::setId() const {
  1613. return _setId;
  1614. }
  1615. QSize StickerSetBox::Inner::boundingBoxSize() const {
  1616. if (isEmojiSet()) {
  1617. using namespace Data;
  1618. const auto size = FrameSizeFromTag(CustomEmojiSizeTag::Large)
  1619. / style::DevicePixelRatio();
  1620. return { size, size };
  1621. }
  1622. return QSize(
  1623. _singleSize.width() - st::roundRadiusSmall * 2,
  1624. _singleSize.height() - st::roundRadiusSmall * 2);
  1625. }
  1626. void StickerSetBox::Inner::visibleTopBottomUpdated(
  1627. int visibleTop,
  1628. int visibleBottom) {
  1629. if (_visibleTop != visibleTop || _visibleBottom != visibleBottom) {
  1630. _visibleTop = visibleTop;
  1631. _visibleBottom = visibleBottom;
  1632. _lastScrolledAt = crl::now();
  1633. update();
  1634. }
  1635. const auto pauseInRows = [&](int fromRow, int tillRow) {
  1636. Expects(fromRow <= tillRow);
  1637. for (auto i = fromRow; i != tillRow; ++i) {
  1638. for (auto j = 0; j != _perRow; ++j) {
  1639. const auto index = i * _perRow + j;
  1640. if (index >= _elements.size()) {
  1641. break;
  1642. }
  1643. if (const auto lottie = _elements[index].lottie) {
  1644. _lottiePlayer->pause(lottie);
  1645. } else if (auto &webm = _elements[index].webm) {
  1646. webm = nullptr;
  1647. }
  1648. }
  1649. }
  1650. };
  1651. const auto rowsTop = _padding.top();
  1652. const auto singleHeight = _singleSize.height();
  1653. const auto rowsBottom = rowsTop + _rowsCount * singleHeight;
  1654. if (visibleTop >= rowsTop + singleHeight && visibleTop < rowsBottom) {
  1655. const auto pauseHeight = (visibleTop - rowsTop);
  1656. const auto pauseRows = std::min(
  1657. pauseHeight / singleHeight,
  1658. _rowsCount);
  1659. pauseInRows(0, pauseRows);
  1660. }
  1661. if (visibleBottom > rowsTop
  1662. && visibleBottom + singleHeight <= rowsBottom) {
  1663. const auto pauseHeight = (rowsBottom - visibleBottom);
  1664. const auto pauseRows = std::min(
  1665. pauseHeight / singleHeight,
  1666. _rowsCount);
  1667. pauseInRows(_rowsCount - pauseRows, _rowsCount);
  1668. }
  1669. }
  1670. void StickerSetBox::Inner::setupLottie(int index) {
  1671. auto &element = _elements[index];
  1672. element.lottie = ChatHelpers::LottieAnimationFromDocument(
  1673. getLottiePlayer(),
  1674. element.documentMedia.get(),
  1675. ChatHelpers::StickerLottieSize::StickerSet,
  1676. boundingBoxSize() * style::DevicePixelRatio());
  1677. }
  1678. void StickerSetBox::Inner::setupWebm(int index) {
  1679. auto &element = _elements[index];
  1680. const auto document = element.document;
  1681. auto callback = [=](Media::Clip::Notification notification) {
  1682. clipCallback(notification, document, index);
  1683. };
  1684. element.webm = Media::Clip::MakeReader(
  1685. element.documentMedia->owner()->location(),
  1686. element.documentMedia->bytes(),
  1687. std::move(callback));
  1688. }
  1689. void StickerSetBox::Inner::clipCallback(
  1690. Media::Clip::Notification notification,
  1691. not_null<DocumentData*> document,
  1692. int index) {
  1693. const auto i = (index < _elements.size()
  1694. && _elements[index].document == document)
  1695. ? (_elements.begin() + index)
  1696. : ranges::find(_elements, document, &Element::document);
  1697. if (i == end(_elements)) {
  1698. return;
  1699. }
  1700. using namespace Media::Clip;
  1701. switch (notification) {
  1702. case Notification::Reinit: {
  1703. auto &webm = i->webm;
  1704. if (webm->state() == State::Error) {
  1705. webm.setBad();
  1706. } else if (webm->ready() && !webm->started()) {
  1707. const auto size = ChatHelpers::ComputeStickerSize(
  1708. i->document,
  1709. boundingBoxSize());
  1710. webm->start({ .frame = size, .keepAlpha = true });
  1711. }
  1712. } break;
  1713. case Notification::Repaint: break;
  1714. }
  1715. updateItems();
  1716. }
  1717. void StickerSetBox::Inner::setupEmoji(int index) {
  1718. auto &element = _elements[index];
  1719. element.emoji = resolveCustomEmoji(element.document);
  1720. }
  1721. not_null<Ui::Text::CustomEmoji*> StickerSetBox::Inner::resolveCustomEmoji(
  1722. not_null<DocumentData*> document) {
  1723. const auto i = _customEmoji.find(document);
  1724. if (i != end(_customEmoji)) {
  1725. return i->second.get();
  1726. }
  1727. auto emoji = document->session().data().customEmojiManager().create(
  1728. document,
  1729. [=] { customEmojiRepaint(); },
  1730. Data::CustomEmojiManager::SizeTag::Large);
  1731. return _customEmoji.emplace(
  1732. document,
  1733. std::move(emoji)
  1734. ).first->second.get();
  1735. }
  1736. void StickerSetBox::Inner::customEmojiRepaint() {
  1737. if (_repaintScheduled) {
  1738. return;
  1739. }
  1740. _repaintScheduled = true;
  1741. update();
  1742. }
  1743. void StickerSetBox::Inner::shakeTransform(
  1744. QPainter &p,
  1745. int index,
  1746. QPoint position,
  1747. crl::time now) const {
  1748. constexpr auto kShakeADuration = crl::time(400);
  1749. constexpr auto kShakeXDuration = crl::time(kShakeADuration * 1.2);
  1750. constexpr auto kShakeYDuration = kShakeADuration;
  1751. const auto diff = ((index % 2) ? 0 : kShakeYDuration / 2)
  1752. + (now - _shakeAnimation.started());
  1753. const auto pX = (diff % kShakeXDuration)
  1754. / float64(kShakeXDuration);
  1755. const auto pY = (diff % kShakeYDuration)
  1756. / float64(kShakeYDuration);
  1757. const auto pA = (diff % kShakeADuration)
  1758. / float64(kShakeADuration);
  1759. constexpr auto kMaxA = 2.;
  1760. constexpr auto kMaxTranslation = .5;
  1761. constexpr auto kAStep = 1. / 5;
  1762. constexpr auto kXStep = 1. / 5;
  1763. constexpr auto kYStep = 1. / 4;
  1764. // 0, -kMaxA, 0, kMaxA, 0.
  1765. const auto angle = (pA < kAStep)
  1766. ? anim::interpolateF(0., -kMaxA, pA / kAStep)
  1767. : (pA < kAStep * 2.)
  1768. ? anim::interpolateF(-kMaxA, 0, (pA - kAStep) / kAStep)
  1769. : (pA < kAStep * 3.)
  1770. ? anim::interpolateF(0, kMaxA, (pA - kAStep * 2.) / kAStep)
  1771. : (pA < kAStep * 4.)
  1772. ? anim::interpolateF(kMaxA, 0, (pA - kAStep * 3.) / kAStep)
  1773. : anim::interpolateF(0, 0., (pA - kAStep * 4.) / kAStep);
  1774. // 0, kMaxTranslation, 0, -kMaxTranslation, 0.
  1775. const auto x = (pX < kXStep)
  1776. ? anim::interpolateF(0., kMaxTranslation, pX / kXStep)
  1777. : (pX < kXStep * 2.)
  1778. ? anim::interpolateF(kMaxTranslation, 0, (pX - kXStep) / kXStep)
  1779. : (pX < kXStep * 3.)
  1780. ? anim::interpolateF(0, -kMaxTranslation, (pX - kXStep * 2.) / kXStep)
  1781. : (pX < kXStep * 4.)
  1782. ? anim::interpolateF(-kMaxTranslation, 0, (pX - kXStep * 3.) / kXStep)
  1783. : anim::interpolateF(0, 0., (pX - kXStep * 4.) / kXStep);
  1784. // 0, kMaxTranslation, -kMaxTranslation, 0.
  1785. const auto y = (pY < kYStep)
  1786. ? anim::interpolateF(0., kMaxTranslation, pY / kYStep)
  1787. : (pY < kYStep * 2.)
  1788. ? anim::interpolateF(kMaxTranslation, 0, (pY - kYStep) / kYStep)
  1789. : (pY < kYStep * 3.)
  1790. ? anim::interpolateF(0, -kMaxTranslation, (pY - kYStep * 2.) / kYStep)
  1791. : anim::interpolateF(-kMaxTranslation, 0, (pY - kYStep * 3) / kYStep);
  1792. const auto center = position + QPoint(
  1793. _singleSize.width() / 2,
  1794. _singleSize.height() / 2);
  1795. p.translate(center);
  1796. p.rotate(angle);
  1797. p.translate(-center);
  1798. p.translate(x, y);
  1799. }
  1800. void StickerSetBox::Inner::paintSticker(
  1801. Painter &p,
  1802. int index,
  1803. QPoint position,
  1804. bool paused,
  1805. crl::time now) const {
  1806. if (_dragging.index != index) {
  1807. const auto over = _elements[index].overAnimation.value(
  1808. (index == _selected) ? 1. : 0.);
  1809. if (over) {
  1810. p.setOpacity(over);
  1811. Ui::FillRoundRect(
  1812. p,
  1813. QRect(
  1814. rtl()
  1815. ? QPoint(
  1816. width() - position.x() - _singleSize.width(),
  1817. position.y())
  1818. : position,
  1819. _singleSize),
  1820. st::emojiPanHover,
  1821. Ui::StickerHoverCorners);
  1822. p.setOpacity(1);
  1823. }
  1824. }
  1825. const auto hasShake = _shakeAnimation.animating();
  1826. if (hasShake) {
  1827. shakeTransform(p, index, position, now);
  1828. }
  1829. const auto &element = _elements[index];
  1830. const auto document = element.document;
  1831. const auto &media = element.documentMedia;
  1832. const auto sticker = document->sticker();
  1833. media->checkStickerSmall();
  1834. if (sticker->setType == Data::StickersType::Emoji) {
  1835. const_cast<Inner*>(this)->setupEmoji(index);
  1836. } else if (media->loaded()) {
  1837. if (sticker->isLottie() && !element.lottie) {
  1838. const_cast<Inner*>(this)->setupLottie(index);
  1839. } else if (sticker->isWebm() && !element.webm) {
  1840. const_cast<Inner*>(this)->setupWebm(index);
  1841. }
  1842. }
  1843. const auto premium = document->isPremiumSticker();
  1844. const auto size = ChatHelpers::ComputeStickerSize(
  1845. document,
  1846. boundingBoxSize());
  1847. const auto ppos = position + QPoint(
  1848. (_singleSize.width() - size.width()) / 2,
  1849. (_singleSize.height() - size.height()) / 2);
  1850. auto lottieFrame = QImage();
  1851. if (element.emoji) {
  1852. element.emoji->paint(p, {
  1853. .textColor = st::windowFg->c,
  1854. .now = now,
  1855. .position = ppos,
  1856. .paused = paused,
  1857. });
  1858. } else if (element.lottie && element.lottie->ready()) {
  1859. lottieFrame = element.lottie->frame();
  1860. p.drawImage(
  1861. QRect(ppos, lottieFrame.size() / style::DevicePixelRatio()),
  1862. lottieFrame);
  1863. _lottiePlayer->unpause(element.lottie);
  1864. } else if (element.webm && element.webm->started()) {
  1865. p.drawImage(ppos, element.webm->current({
  1866. .frame = size,
  1867. .keepAlpha = true,
  1868. }, paused ? 0 : now));
  1869. } else if (const auto image = media->getStickerSmall()) {
  1870. const auto pixmap = image->pix(size);
  1871. p.drawPixmapLeft(ppos, width(), pixmap);
  1872. if (premium) {
  1873. lottieFrame = pixmap.toImage().convertToFormat(
  1874. QImage::Format_ARGB32_Premultiplied);
  1875. }
  1876. } else {
  1877. ChatHelpers::PaintStickerThumbnailPath(
  1878. p,
  1879. media.get(),
  1880. QRect(ppos, size),
  1881. _pathGradient.get());
  1882. }
  1883. if (premium) {
  1884. _premiumMark.paint(
  1885. p,
  1886. lottieFrame,
  1887. element.premiumLock,
  1888. position,
  1889. _singleSize,
  1890. width());
  1891. }
  1892. if (hasShake) {
  1893. p.resetTransform();
  1894. }
  1895. }
  1896. bool StickerSetBox::Inner::loaded() const {
  1897. return _loaded && !_pack.isEmpty();
  1898. }
  1899. bool StickerSetBox::Inner::premiumEmojiSet() const {
  1900. return (_setFlags & SetFlag::Emoji)
  1901. && !_pack.empty()
  1902. && _pack.front()->isPremiumEmoji();
  1903. }
  1904. bool StickerSetBox::Inner::notInstalled() const {
  1905. if (!_loaded) {
  1906. return false;
  1907. }
  1908. const auto &sets = _session->data().stickers().sets();
  1909. const auto it = sets.find(_setId);
  1910. if ((it == sets.cend())
  1911. || !(it->second->flags & SetFlag::Installed)
  1912. || (it->second->flags & SetFlag::Archived)) {
  1913. return !_pack.empty();
  1914. }
  1915. return false;
  1916. }
  1917. bool StickerSetBox::Inner::official() const {
  1918. return _loaded && _setShortName.isEmpty();
  1919. }
  1920. rpl::producer<TextWithEntities> StickerSetBox::Inner::title() const {
  1921. if (!_loaded) {
  1922. return tr::lng_contacts_loading() | Ui::Text::ToWithEntities();
  1923. } else if (_pack.isEmpty()) {
  1924. return tr::lng_attach_failed() | Ui::Text::ToWithEntities();
  1925. }
  1926. auto text = TextWithEntities{ _setTitle };
  1927. TextUtilities::ParseEntities(text, TextParseMentions);
  1928. return rpl::single(text);
  1929. }
  1930. QString StickerSetBox::Inner::shortName() const {
  1931. return _setShortName;
  1932. }
  1933. void StickerSetBox::Inner::install() {
  1934. if (_installRequest) {
  1935. return;
  1936. }
  1937. _installRequest = _api.request(MTPmessages_InstallStickerSet(
  1938. Data::InputStickerSet(_input),
  1939. MTP_bool(false)
  1940. )).done([=](const MTPmessages_StickerSetInstallResult &result) {
  1941. installDone(result);
  1942. }).fail([=] {
  1943. _errors.fire(Error::NotFound);
  1944. }).send();
  1945. }
  1946. void StickerSetBox::Inner::archiveStickers() {
  1947. _api.request(MTPmessages_InstallStickerSet(
  1948. Data::InputStickerSet(_input),
  1949. MTP_boolTrue()
  1950. )).done([=](const MTPmessages_StickerSetInstallResult &result) {
  1951. if (result.type() == mtpc_messages_stickerSetInstallResultSuccess) {
  1952. _setArchived.fire_copy(_setId);
  1953. }
  1954. }).fail([=] {
  1955. _show->showToast(Lang::Hard::ServerError());
  1956. }).send();
  1957. }
  1958. void StickerSetBox::Inner::updateItems() {
  1959. const auto now = crl::now();
  1960. const auto delay = std::max(
  1961. _lastScrolledAt + kMinAfterScrollDelay - now,
  1962. _lastUpdatedAt + kMinRepaintDelay - now);
  1963. if (delay <= 0) {
  1964. repaintItems(now);
  1965. } else if (!_updateItemsTimer.isActive()
  1966. || _updateItemsTimer.remainingTime() > kMinRepaintDelay) {
  1967. _updateItemsTimer.callOnce(std::max(delay, kMinRepaintDelay));
  1968. }
  1969. }
  1970. void StickerSetBox::Inner::repaintItems(crl::time now) {
  1971. _lastUpdatedAt = now ? now : crl::now();
  1972. update();
  1973. }
  1974. StickerSetBox::Inner::~Inner() = default;