info_userpic_emoji_builder_widget.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  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 "info/userpic/info_userpic_emoji_builder_widget.h"
  8. #include "api/api_peer_photo.h"
  9. #include "apiwrap.h"
  10. #include "chat_helpers/emoji_list_widget.h"
  11. #include "chat_helpers/stickers_list_widget.h"
  12. #include "data/data_document.h"
  13. #include "data/data_message_reactions.h"
  14. #include "data/data_session.h"
  15. #include "data/stickers/data_custom_emoji.h"
  16. #include "editor/photo_editor_layer_widget.h" // Editor::kProfilePhotoSize.
  17. #include "info/userpic/info_userpic_bubble_wrap.h"
  18. #include "info/userpic/info_userpic_color_circle_button.h"
  19. #include "info/userpic/info_userpic_colors_editor.h"
  20. #include "info/userpic/info_userpic_emoji_builder_common.h"
  21. #include "info/userpic/info_userpic_emoji_builder_preview.h"
  22. #include "lang/lang_keys.h"
  23. #include "main/main_session.h"
  24. #include "ui/controls/emoji_button.h"
  25. #include "ui/empty_userpic.h"
  26. #include "ui/layers/generic_box.h"
  27. #include "ui/painter.h"
  28. #include "ui/rect.h"
  29. #include "ui/widgets/labels.h"
  30. #include "ui/widgets/scroll_area.h"
  31. #include "ui/wrap/padding_wrap.h"
  32. #include "ui/wrap/vertical_layout.h"
  33. #include "window/window_session_controller.h"
  34. #include "styles/style_chat.h"
  35. #include "styles/style_chat_helpers.h"
  36. #include "styles/style_info_userpic_builder.h"
  37. #include "styles/style_layers.h"
  38. #include "styles/style_settings.h"
  39. #include "styles/style_menu_icons.h"
  40. namespace UserpicBuilder {
  41. namespace {
  42. void AlignChildren(not_null<Ui::RpWidget*> widget, int fullWidth) {
  43. const auto children = widget->children();
  44. const auto widgets = ranges::views::all(
  45. children
  46. ) | ranges::views::filter([](not_null<const QObject*> object) {
  47. return object->isWidgetType();
  48. }) | ranges::views::transform([](not_null<QObject*> object) {
  49. return static_cast<QWidget*>(object.get());
  50. }) | ranges::to_vector;
  51. const auto widgetWidth = widgets.front()->width();
  52. const auto widgetsCount = widgets.size();
  53. const auto widgetsWidth = widgetWidth * widgetsCount;
  54. const auto step = (fullWidth - widgetsWidth) / (widgetsCount - 1);
  55. for (auto i = 0; i < widgetsCount; i++) {
  56. widgets[i]->move(i * (widgetWidth + step), widgets[i]->y());
  57. }
  58. }
  59. [[nodiscard]] QImage GenerateSpecial(
  60. int size,
  61. const std::vector<QColor> colors) {
  62. if (colors.empty()) {
  63. auto image = QImage(
  64. Size(size * style::DevicePixelRatio()),
  65. QImage::Format_ARGB32_Premultiplied);
  66. image.setDevicePixelRatio(style::DevicePixelRatio());
  67. image.fill(Qt::transparent);
  68. {
  69. auto p = QPainter(&image);
  70. st::userpicBuilderEmojiColorPlus.icon.paintInCenter(
  71. p,
  72. Rect(Size(size)));
  73. }
  74. return image;
  75. } else {
  76. auto image = GenerateGradient(Size(size), colors);
  77. {
  78. auto p = QPainter(&image);
  79. constexpr auto kEllipseSize = 1;
  80. const auto center = QPointF(size / 2., size / 2.);
  81. const auto shift = QPointF(kEllipseSize * 4, 0);
  82. p.setPen(Qt::NoPen);
  83. p.setBrush(st::boxBg);
  84. p.drawEllipse(center, kEllipseSize, kEllipseSize);
  85. p.drawEllipse(center + shift, kEllipseSize, kEllipseSize);
  86. p.drawEllipse(center - shift, kEllipseSize, kEllipseSize);
  87. }
  88. return image;
  89. }
  90. }
  91. [[nodiscard]] std::vector<std::vector<QColor>> PaletteGradients() {
  92. auto v = std::vector<std::vector<QColor>>{
  93. {
  94. QColor(32, 226, 205),
  95. QColor(14, 225, 241),
  96. QColor(77, 141, 255),
  97. QColor(43, 191, 255),
  98. },
  99. {
  100. QColor(69, 247, 183),
  101. QColor(31, 241, 217),
  102. QColor(94, 182, 251),
  103. QColor(31, 206, 235),
  104. },
  105. {
  106. QColor(193, 229, 38),
  107. QColor(128, 223, 43),
  108. QColor(9, 210, 96),
  109. QColor(94, 220, 64),
  110. },
  111. {
  112. QColor(255, 212, 18),
  113. QColor(255, 167, 67),
  114. QColor(245, 105, 78),
  115. QColor(245, 119, 44),
  116. },
  117. {
  118. QColor(246, 167, 48),
  119. QColor(255, 119, 66),
  120. QColor(246, 72, 132),
  121. QColor(239, 91, 65),
  122. },
  123. {
  124. QColor(255, 178, 58),
  125. QColor(254, 126, 98),
  126. QColor(249, 75, 160),
  127. QColor(251, 92, 128),
  128. },
  129. {
  130. QColor(255, 114, 169),
  131. QColor(226, 105, 255),
  132. QColor(131, 124, 255),
  133. QColor(176, 99, 255),
  134. },
  135. };
  136. for (auto &g : v) {
  137. // Rotate 180 degrees.
  138. std::swap(g[0], g[2]);
  139. std::swap(g[1], g[3]);
  140. }
  141. return v;
  142. }
  143. void ShowGradientEditor(
  144. not_null<Window::SessionController*> controller,
  145. StartData data,
  146. Fn<void(std::vector<QColor>)> &&doneCallback) {
  147. controller->show(Box([=](not_null<Ui::GenericBox*> box) {
  148. struct State {
  149. rpl::event_stream<> saveRequests;
  150. };
  151. const auto state = box->lifetime().make_state<State>();
  152. box->setTitle(tr::lng_chat_theme_change());
  153. box->addButton(tr::lng_settings_save(), [=] {
  154. state->saveRequests.fire({});
  155. });
  156. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  157. auto content = CreateGradientEditor(
  158. box,
  159. (data.documentId
  160. ? controller->session().data().document(
  161. data.documentId).get()
  162. : nullptr),
  163. data.gradientEditorColors,
  164. BothWayCommunication<std::vector<QColor>>{
  165. state->saveRequests.events(),
  166. [=](std::vector<QColor> colors) {
  167. box->closeBox();
  168. doneCallback(std::move(colors));
  169. },
  170. });
  171. box->setWidth(content->width());
  172. box->addRow(std::move(content), {});
  173. }));
  174. }
  175. class EmojiSelector final : public Ui::RpWidget {
  176. public:
  177. EmojiSelector(
  178. not_null<Ui::RpWidget*> parent,
  179. not_null<Window::SessionController*> controller,
  180. rpl::producer<std::vector<DocumentId>> recent);
  181. [[nodiscard]] rpl::producer<not_null<DocumentData*>> chosen() const;
  182. private:
  183. using Footer = ChatHelpers::TabbedSelector::InnerFooter;
  184. using List = ChatHelpers::TabbedSelector::Inner;
  185. using Type = ChatHelpers::SelectorTab;
  186. void createSelector(Type type);
  187. struct Selector {
  188. not_null<List*> list;
  189. not_null<Footer*> footer;
  190. };
  191. [[nodiscard]] Selector createEmojiList(
  192. not_null<Ui::ScrollArea*> scroll);
  193. [[nodiscard]] Selector createStickersList(
  194. not_null<Ui::ScrollArea*> scroll) const;
  195. const not_null<Window::SessionController*> _controller;
  196. base::unique_qptr<Ui::RpWidget> _container;
  197. rpl::event_stream<> _recentChanges;
  198. std::vector<DocumentId> _lastRecent;
  199. rpl::event_stream<not_null<DocumentData*>> _chosen;
  200. };
  201. EmojiSelector::EmojiSelector(
  202. not_null<Ui::RpWidget*> parent,
  203. not_null<Window::SessionController*> controller,
  204. rpl::producer<std::vector<DocumentId>> recent)
  205. : RpWidget(parent)
  206. , _controller(controller) {
  207. std::move(
  208. recent
  209. ) | rpl::start_with_next([=](std::vector<DocumentId> ids) {
  210. _lastRecent = std::move(ids);
  211. _recentChanges.fire({});
  212. }, lifetime());
  213. createSelector(Type::Emoji);
  214. }
  215. rpl::producer<not_null<DocumentData*>> EmojiSelector::chosen() const {
  216. return _chosen.events();
  217. }
  218. EmojiSelector::Selector EmojiSelector::createEmojiList(
  219. not_null<Ui::ScrollArea*> scroll) {
  220. const auto session = &_controller->session();
  221. const auto manager = &session->data().customEmojiManager();
  222. const auto tag = Data::CustomEmojiManager::SizeTag::Large;
  223. auto args = ChatHelpers::EmojiListDescriptor{
  224. .show = _controller->uiShow(),
  225. .mode = ChatHelpers::EmojiListMode::UserpicBuilder,
  226. .paused = [=] { return true; },
  227. .customRecentList = ChatHelpers::DocumentListToRecent(_lastRecent),
  228. .customRecentFactory = [=](DocumentId id, Fn<void()> repaint) {
  229. return manager->create(id, std::move(repaint), tag);
  230. },
  231. .st = &st::userpicBuilderEmojiPan,
  232. };
  233. const auto list = scroll->setOwnedWidget(
  234. object_ptr<ChatHelpers::EmojiListWidget>(scroll, std::move(args)));
  235. const auto footer = list->createFooter().data();
  236. list->refreshEmoji();
  237. list->customChosen(
  238. ) | rpl::start_with_next([=](const ChatHelpers::FileChosen &chosen) {
  239. _chosen.fire_copy(chosen.document);
  240. }, list->lifetime());
  241. _recentChanges.events(
  242. ) | rpl::start_with_next([=] {
  243. createSelector(Type::Emoji);
  244. }, list->lifetime());
  245. list->setAllowWithoutPremium(true);
  246. return { list, footer };
  247. }
  248. EmojiSelector::Selector EmojiSelector::createStickersList(
  249. not_null<Ui::ScrollArea*> scroll) const {
  250. const auto list = scroll->setOwnedWidget(
  251. object_ptr<ChatHelpers::StickersListWidget>(
  252. scroll,
  253. _controller,
  254. Window::GifPauseReason::Any,
  255. ChatHelpers::StickersListMode::UserpicBuilder));
  256. const auto footer = list->createFooter().data();
  257. list->refreshRecent();
  258. list->chosen(
  259. ) | rpl::start_with_next([=](const ChatHelpers::FileChosen &chosen) {
  260. _chosen.fire_copy(chosen.document);
  261. }, list->lifetime());
  262. return { list, footer };
  263. }
  264. void EmojiSelector::createSelector(Type type) {
  265. Expects((type == Type::Emoji) || (type == Type::Stickers));
  266. const auto isEmoji = (type == Type::Emoji);
  267. const auto &stScroll = st::reactPanelScroll;
  268. _container = base::make_unique_q<Ui::RpWidget>(this);
  269. const auto container = _container.get();
  270. container->show();
  271. sizeValue(
  272. ) | rpl::start_with_next([=](const QSize &s) {
  273. container->setGeometry(Rect(s));
  274. }, container->lifetime());
  275. const auto scroll = Ui::CreateChild<Ui::ScrollArea>(container, stScroll);
  276. const auto selector = isEmoji
  277. ? createEmojiList(scroll)
  278. : createStickersList(scroll);
  279. selector.footer->setParent(container);
  280. const auto toggleButton = Ui::CreateChild<Ui::AbstractButton>(container);
  281. const auto &togglePos = st::userpicBuilderEmojiSelectorTogglePosition;
  282. {
  283. const auto &pos = togglePos;
  284. toggleButton->resize(st::menuIconStickers.size()
  285. // Trying to overlap the settings button under.
  286. + QSize(pos.x() * 2, pos.y() * 2));
  287. toggleButton->show();
  288. toggleButton->paintRequest(
  289. ) | rpl::start_with_next([=] {
  290. auto p = QPainter(toggleButton);
  291. const auto r = toggleButton->rect()
  292. - QMargins(pos.x(), pos.y(), pos.x(), pos.y());
  293. p.fillRect(r, st::boxBg);
  294. if (isEmoji) {
  295. st::userpicBuilderEmojiToggleStickersIcon.paintInCenter(p, r);
  296. } else {
  297. st::defaultEmojiPan.icons.people.paintInCenter(p, r);
  298. }
  299. }, toggleButton->lifetime());
  300. }
  301. toggleButton->show();
  302. toggleButton->setClickedCallback([=] {
  303. createSelector(isEmoji ? Type::Stickers : Type::Emoji);
  304. });
  305. rpl::combine(
  306. scroll->scrollTopValue(),
  307. scroll->heightValue()
  308. ) | rpl::start_with_next([=](int scrollTop, int scrollHeight) {
  309. const auto scrollBottom = scrollTop + scrollHeight;
  310. selector.list->setVisibleTopBottom(scrollTop, scrollBottom);
  311. }, selector.list->lifetime());
  312. selector.list->scrollToRequests(
  313. ) | rpl::start_with_next([=](int y) {
  314. scroll->scrollToY(y);
  315. // _shadow->update();
  316. }, selector.list->lifetime());
  317. const auto separator = Ui::CreateChild<Ui::RpWidget>(container);
  318. separator->paintRequest(
  319. ) | rpl::start_with_next([=](const QRect &r) {
  320. auto p = QPainter(separator);
  321. p.fillRect(r, st::shadowFg);
  322. }, separator->lifetime());
  323. selector.footer->show();
  324. separator->show();
  325. scroll->show();
  326. const auto scrollWidth = stScroll.width;
  327. sizeValue(
  328. ) | rpl::start_with_next([=](const QSize &s) {
  329. const auto left = st::userpicBuilderEmojiSelectorLeft;
  330. const auto mostTop = st::userpicBuilderEmojiSelectorLeft;
  331. toggleButton->move(QPoint(left, mostTop));
  332. selector.footer->setGeometry(
  333. (isEmoji ? (rect::right(toggleButton) - togglePos.x()) : left),
  334. mostTop,
  335. s.width() - left,
  336. selector.footer->height());
  337. separator->setGeometry(
  338. 0,
  339. rect::bottom(selector.footer),
  340. s.width(),
  341. st::lineWidth);
  342. const auto listWidth = s.width() - st::boxRadius * 2;
  343. selector.list->resizeToWidth(listWidth);
  344. scroll->setGeometry(
  345. st::boxRadius,
  346. rect::bottom(separator),
  347. selector.list->width() + scrollWidth,
  348. s.height() - rect::bottom(separator));
  349. selector.list->setMinimalHeight(listWidth, scroll->height());
  350. }, lifetime());
  351. // Reset all animations.
  352. selector.list->hideFinished();
  353. }
  354. } // namespace
  355. not_null<Ui::VerticalLayout*> CreateUserpicBuilder(
  356. not_null<Ui::RpWidget*> parent,
  357. not_null<Window::SessionController*> controller,
  358. StartData data,
  359. BothWayCommunication<UserpicBuilder::Result> communication) {
  360. const auto container = Ui::CreateChild<Ui::VerticalLayout>(parent.get());
  361. struct State {
  362. std::vector<not_null<CircleButton*>> circleButtons;
  363. Ui::Animations::Simple chosenColorAnimation;
  364. int colorIndex = -1;
  365. std::vector<QColor> editorColors;
  366. StartData gradientEditorStartData;
  367. };
  368. const auto state = container->lifetime().make_state<State>();
  369. const auto preview = container->add(
  370. object_ptr<Ui::CenterWrap<EmojiUserpic>>(
  371. container,
  372. object_ptr<EmojiUserpic>(
  373. container,
  374. Size(st::settingsInfoPhotoSize),
  375. data.isForum)),
  376. st::userpicBuilderEmojiPreviewPadding)->entity();
  377. if (const auto id = data.documentId) {
  378. const auto document = controller->session().data().document(id);
  379. if (document && document->sticker()) {
  380. preview->setDocument(document);
  381. }
  382. }
  383. container->add(
  384. object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
  385. container,
  386. object_ptr<Ui::FlatLabel>(
  387. container,
  388. tr::lng_userpic_builder_color_subtitle(),
  389. st::userpicBuilderEmojiSubtitle)),
  390. st::userpicBuilderEmojiSubtitlePadding);
  391. const auto paletteBg = Ui::AddBubbleWrap(
  392. container,
  393. QSize(
  394. st::userpicBuilderEmojiBubblePaletteWidth,
  395. std::abs(Ui::BubbleWrapInnerRect(QRect(0, 0, 0, 0)).height())
  396. + st::userpicBuilderEmojiAccentColorSize
  397. + rect::m::sum::v(
  398. st::userpicBuilderEmojiBubblePalettePadding)));
  399. const auto palette = Ui::CreateChild<Ui::VerticalLayout>(paletteBg.get());
  400. {
  401. constexpr auto kColorsCount = int(7);
  402. const auto checkIsSpecial = [=](int i) {
  403. return (i == kColorsCount);
  404. };
  405. const auto size = st::userpicBuilderEmojiAccentColorSize;
  406. const auto paletteGradients = PaletteGradients();
  407. for (auto i = 0; i < kColorsCount + 1; i++) {
  408. const auto isSpecial = checkIsSpecial(i);
  409. const auto colors = paletteGradients[i % kColorsCount];
  410. const auto button = Ui::CreateChild<CircleButton>(palette);
  411. state->circleButtons.push_back(button);
  412. button->resize(size, size);
  413. button->setBrush(isSpecial
  414. ? GenerateSpecial(size, state->editorColors)
  415. : GenerateGradient(Size(size), colors));
  416. const auto openEditor = isSpecial
  417. ? Fn<void()>([=] {
  418. if (checkIsSpecial(state->colorIndex)) {
  419. state->colorIndex = -1;
  420. }
  421. ShowGradientEditor(
  422. controller,
  423. state->gradientEditorStartData,
  424. [=](std::vector<QColor> colors) {
  425. state->editorColors = std::move(colors);
  426. button->setBrush(
  427. GenerateSpecial(size, state->editorColors));
  428. button->clicked({}, Qt::LeftButton);
  429. });
  430. })
  431. : nullptr;
  432. button->setClickedCallback([=] {
  433. if (openEditor && state->editorColors.empty()) {
  434. return openEditor();
  435. }
  436. const auto was = state->colorIndex;
  437. const auto now = i;
  438. if (was == now) {
  439. if (openEditor) {
  440. openEditor();
  441. }
  442. return;
  443. }
  444. state->chosenColorAnimation.stop();
  445. state->chosenColorAnimation.start([=](float64 progress) {
  446. if (was >= 0) {
  447. state->circleButtons[was]->setSelectedProgress(
  448. 1. - progress);
  449. }
  450. state->circleButtons[now]->setSelectedProgress(progress);
  451. }, 0., 1., st::universalDuration);
  452. state->colorIndex = now;
  453. const auto result = isSpecial
  454. ? state->editorColors
  455. : colors;
  456. state->gradientEditorStartData.gradientEditorColors = result;
  457. preview->setGradientColors(result);
  458. });
  459. }
  460. const auto current = data.builderColorIndex % kColorsCount;
  461. state->circleButtons[current]->setSelectedProgress(1.);
  462. state->circleButtons[current]->clicked({}, Qt::LeftButton);
  463. }
  464. paletteBg->sizeValue(
  465. ) | rpl::start_with_next([=](const QSize &s) {
  466. palette->setGeometry(Ui::BubbleWrapInnerRect(Rect(s))
  467. - st::userpicBuilderEmojiBubblePalettePadding);
  468. AlignChildren(palette, palette->width());
  469. }, palette->lifetime());
  470. container->add(
  471. object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
  472. container,
  473. object_ptr<Ui::FlatLabel>(
  474. container,
  475. tr::lng_userpic_builder_emoji_subtitle(),
  476. st::userpicBuilderEmojiSubtitle)),
  477. st::userpicBuilderEmojiSubtitlePadding);
  478. const auto selectorBg = Ui::AddBubbleWrap(
  479. container,
  480. QSize(
  481. st::userpicBuilderEmojiBubblePaletteWidth,
  482. st::userpicBuilderEmojiSelectorMinHeight));
  483. const auto selector = Ui::CreateChild<EmojiSelector>(
  484. selectorBg.get(),
  485. controller,
  486. base::take(data.documents));
  487. selector->chosen(
  488. ) | rpl::start_with_next([=](not_null<DocumentData*> document) {
  489. state->gradientEditorStartData.documentId = document->id;
  490. preview->setDocument(document);
  491. }, preview->lifetime());
  492. selectorBg->sizeValue(
  493. ) | rpl::start_with_next([=](const QSize &s) {
  494. selector->setGeometry(Ui::BubbleWrapInnerRect(Rect(s)));
  495. }, selector->lifetime());
  496. base::take(
  497. communication.triggers
  498. ) | rpl::start_with_next([=, done = base::take(communication.result)] {
  499. preview->result(Editor::kProfilePhotoSize, [=](Result result) {
  500. done(std::move(result));
  501. });
  502. }, preview->lifetime());
  503. return container;
  504. }
  505. not_null<Ui::RpWidget*> CreateEmojiUserpic(
  506. not_null<Ui::RpWidget*> parent,
  507. const QSize &size,
  508. rpl::producer<not_null<DocumentData*>> document,
  509. rpl::producer<int> colorIndex,
  510. bool isForum) {
  511. const auto paletteGradients = PaletteGradients();
  512. const auto widget = Ui::CreateChild<EmojiUserpic>(
  513. parent.get(),
  514. size,
  515. isForum);
  516. std::move(
  517. document
  518. ) | rpl::start_with_next([=](not_null<DocumentData*> d) {
  519. widget->setDocument(d);
  520. }, widget->lifetime());
  521. std::move(
  522. colorIndex
  523. ) | rpl::start_with_next([=](int index) {
  524. widget->setGradientColors(
  525. paletteGradients[index % paletteGradients.size()]);
  526. }, widget->lifetime());
  527. return widget;
  528. }
  529. } // namespace UserpicBuilder