peer_qr_box.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981
  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 "ui/boxes/peer_qr_box.h"
  8. #include "core/application.h"
  9. #include "data/data_cloud_themes.h"
  10. #include "data/data_peer.h"
  11. #include "data/data_session.h"
  12. #include "data/data_user.h"
  13. #include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
  14. #include "info/profile/info_profile_values.h"
  15. #include "lang/lang_keys.h"
  16. #include "main/main_session.h"
  17. #include "qr/qr_generate.h"
  18. #include "ui/controls/userpic_button.h"
  19. #include "ui/dynamic_image.h"
  20. #include "ui/dynamic_thumbnails.h"
  21. #include "ui/effects/animations.h"
  22. #include "ui/image/image_prepare.h"
  23. #include "ui/layers/generic_box.h"
  24. #include "ui/painter.h"
  25. #include "ui/rect.h"
  26. #include "ui/ui_utility.h"
  27. #include "ui/vertical_list.h"
  28. #include "ui/widgets/box_content_divider.h"
  29. #include "ui/widgets/buttons.h"
  30. #include "ui/widgets/continuous_sliders.h"
  31. #include "ui/wrap/vertical_layout.h"
  32. #include "window/window_controller.h"
  33. #include "window/window_session_controller.h"
  34. #include "styles/style_boxes.h"
  35. #include "styles/style_giveaway.h"
  36. #include "styles/style_credits.h"
  37. #include "styles/style_intro.h"
  38. #include "styles/style_layers.h"
  39. #include "styles/style_settings.h"
  40. #include "styles/style_widgets.h"
  41. #include "styles/style_window.h"
  42. #include <QtCore/QMimeData>
  43. #include <QtGui/QGuiApplication>
  44. #include <QtSvg/QSvgRenderer>
  45. namespace Ui {
  46. namespace {
  47. using Colors = std::vector<QColor>;
  48. [[nodiscard]] QMargins NoPhotoBackgroundMargins() {
  49. return QMargins(
  50. st::profileQrBackgroundMargins.left(),
  51. st::profileQrBackgroundMargins.left(),
  52. st::profileQrBackgroundMargins.right(),
  53. st::profileQrBackgroundMargins.bottom());
  54. }
  55. [[nodiscard]] style::font CreateFont(int size, int scale) {
  56. return style::font(
  57. style::ConvertScale(size, scale),
  58. st::profileQrFont->flags(),
  59. st::profileQrFont->family());
  60. }
  61. [[nodiscard]] QImage TelegramQr(
  62. const Qr::Data &data,
  63. int pixel,
  64. int max,
  65. bool hasWhiteBackground) {
  66. Expects(data.size > 0);
  67. constexpr auto kCenterRatio = 0.175;
  68. if (max > 0 && data.size * pixel > max) {
  69. pixel = std::max(max / data.size, 1);
  70. }
  71. auto qr = Qr::Generate(
  72. data,
  73. pixel * style::DevicePixelRatio(),
  74. hasWhiteBackground ? Qt::transparent : Qt::black,
  75. hasWhiteBackground ? Qt::white : Qt::transparent);
  76. {
  77. auto p = QPainter(&qr);
  78. auto hq = PainterHighQualityEnabler(p);
  79. auto svg = QSvgRenderer(u":/gui/plane_white.svg"_q);
  80. const auto size = qr.rect().size();
  81. const auto centerRect = Rect(size)
  82. - Margins((size.width() - (size.width() * kCenterRatio)) / 2);
  83. p.setPen(Qt::NoPen);
  84. p.setBrush(Qt::white);
  85. if (hasWhiteBackground) {
  86. p.setCompositionMode(QPainter::CompositionMode_Clear);
  87. p.drawEllipse(centerRect);
  88. p.setCompositionMode(QPainter::CompositionMode_SourceOver);
  89. svg.render(&p, centerRect);
  90. } else {
  91. p.drawEllipse(centerRect);
  92. p.setCompositionMode(QPainter::CompositionMode_Clear);
  93. svg.render(&p, centerRect);
  94. }
  95. }
  96. return qr;
  97. }
  98. [[nodiscard]] QMargins RoundedMargins(
  99. const QMargins &backgroundMargins,
  100. int photoSize,
  101. int textMaxHeight) {
  102. return (textMaxHeight
  103. ? (backgroundMargins + QMargins(0, photoSize / 2, 0, textMaxHeight))
  104. : photoSize
  105. ? backgroundMargins + QMargins(0, photoSize / 2, 0, photoSize / 2)
  106. : Margins(backgroundMargins.left()));
  107. }
  108. void Paint(
  109. QPainter &p,
  110. const style::font &font,
  111. const QString &text,
  112. const Colors &backgroundColors,
  113. const QMargins &backgroundMargins,
  114. const QImage &qrImage,
  115. const QRect &qrRect,
  116. int qrMaxSize,
  117. int qrPixel,
  118. int radius,
  119. int textMaxHeight,
  120. int photoSize,
  121. bool hasWhiteBackground) {
  122. auto hq = PainterHighQualityEnabler(p);
  123. p.setPen(Qt::NoPen);
  124. p.setBrush(hasWhiteBackground ? Qt::white : Qt::transparent);
  125. const auto roundedRect = qrRect
  126. + RoundedMargins(backgroundMargins, photoSize, textMaxHeight);
  127. p.drawRoundedRect(roundedRect, radius, radius);
  128. if (!qrImage.isNull() && !backgroundColors.empty()) {
  129. constexpr auto kDuration = crl::time(10000);
  130. const auto angle = (crl::now() % kDuration)
  131. / float64(kDuration) * 360.0;
  132. const auto gradientRotation = int(angle / 45.) * 45;
  133. const auto gradientRotationAdd = angle - gradientRotation;
  134. const auto textAdditionalWidth = backgroundMargins.left();
  135. auto back = Images::GenerateGradient(
  136. qrRect.size() + QSize(textAdditionalWidth, 0),
  137. backgroundColors,
  138. gradientRotation,
  139. 1. - (gradientRotationAdd / 45.));
  140. if (hasWhiteBackground) {
  141. p.drawImage(qrRect, back);
  142. }
  143. const auto coloredSize = QSize(back.width(), textMaxHeight);
  144. auto colored = QImage(
  145. coloredSize * style::DevicePixelRatio(),
  146. QImage::Format_ARGB32_Premultiplied);
  147. colored.setDevicePixelRatio(style::DevicePixelRatio());
  148. colored.fill(Qt::transparent);
  149. if (textMaxHeight) {
  150. // '@' + QString(32, 'W');
  151. auto p = QPainter(&colored);
  152. auto hq = PainterHighQualityEnabler(p);
  153. p.setPen(Qt::black);
  154. p.setFont(font);
  155. auto option = QTextOption(style::al_center);
  156. option.setWrapMode(QTextOption::WrapAnywhere);
  157. p.drawText(Rect(coloredSize), text, option);
  158. p.setCompositionMode(QPainter::CompositionMode_SourceIn);
  159. p.drawImage(0, -back.height() + textMaxHeight, back);
  160. }
  161. if (!hasWhiteBackground) {
  162. auto copy = qrImage;
  163. {
  164. auto p = QPainter(&copy);
  165. p.setCompositionMode(QPainter::CompositionMode_SourceIn);
  166. p.drawImage(Rect(copy.size()), back);
  167. }
  168. p.drawImage(qrRect, copy);
  169. } else {
  170. p.drawImage(qrRect, qrImage);
  171. }
  172. if (textMaxHeight) {
  173. p.drawImage(
  174. qrRect.x() - textAdditionalWidth / 2,
  175. rect::bottom(qrRect)
  176. + ((rect::bottom(roundedRect) - rect::bottom(qrRect))
  177. - textMaxHeight) / 2,
  178. colored);
  179. }
  180. }
  181. }
  182. not_null<Ui::RpWidget*> PrepareQrWidget(
  183. not_null<Ui::VerticalLayout*> container,
  184. not_null<Ui::RpWidget*> topWidget,
  185. rpl::producer<int> fontSizeValue,
  186. rpl::producer<bool> userpicToggled,
  187. rpl::producer<bool> backgroundToggled,
  188. rpl::producer<QString> username,
  189. rpl::producer<QString> links,
  190. rpl::producer<Colors> bgs,
  191. rpl::producer<QString> about) {
  192. const auto divider = container->add(
  193. object_ptr<Ui::BoxContentDivider>(container));
  194. struct State final {
  195. explicit State(Fn<void()> callback) : updating(callback) {
  196. updating.start();
  197. }
  198. Ui::Animations::Basic updating;
  199. style::font font;
  200. QImage qrImage;
  201. Colors backgroundColors;
  202. QString text;
  203. QMargins backgroundMargins;
  204. int textWidth = 0;
  205. int textMaxHeight = 0;
  206. int photoSize = 0;
  207. bool backgroundToggled = false;
  208. };
  209. const auto result = Ui::CreateChild<Ui::RpWidget>(divider);
  210. topWidget->setParent(result);
  211. topWidget->setAttribute(Qt::WA_TransparentForMouseEvents);
  212. const auto state = result->lifetime().make_state<State>(
  213. [=] { result->update(); });
  214. const auto qrMaxSize = st::boxWideWidth
  215. - rect::m::sum::h(st::boxRowPadding)
  216. - rect::m::sum::h(st::profileQrBackgroundMargins);
  217. const auto aboutLabel = Ui::CreateChild<Ui::FlatLabel>(
  218. divider,
  219. st::creditsBoxAboutDivider);
  220. rpl::combine(
  221. std::move(fontSizeValue),
  222. std::move(userpicToggled),
  223. std::move(backgroundToggled),
  224. std::move(username),
  225. std::move(bgs),
  226. std::move(links),
  227. std::move(about),
  228. rpl::single(rpl::empty) | rpl::then(style::PaletteChanged())
  229. ) | rpl::start_with_next([=](
  230. int fontSize,
  231. bool userpicToggled,
  232. bool backgroundToggled,
  233. const QString &username,
  234. const Colors &backgroundColors,
  235. const QString &link,
  236. const QString &about,
  237. const auto &) {
  238. state->font = CreateFont(fontSize, style::Scale());
  239. state->backgroundToggled = backgroundToggled;
  240. state->backgroundMargins = userpicToggled
  241. ? st::profileQrBackgroundMargins
  242. : NoPhotoBackgroundMargins();
  243. state->photoSize = userpicToggled
  244. ? st::defaultUserpicButton.photoSize
  245. : 0;
  246. state->backgroundColors = backgroundColors;
  247. state->text = username.toUpper();
  248. state->textWidth = state->font->width(state->text);
  249. {
  250. const auto remainder = qrMaxSize % st::introQrPixel;
  251. const auto downTo = remainder
  252. ? qrMaxSize - remainder
  253. : qrMaxSize;
  254. state->qrImage = TelegramQr(
  255. Qr::Encode(link.toUtf8(), Qr::Redundancy::Default),
  256. st::introQrPixel,
  257. downTo,
  258. backgroundToggled).scaled(
  259. Size(qrMaxSize * style::DevicePixelRatio()),
  260. Qt::IgnoreAspectRatio,
  261. Qt::SmoothTransformation);
  262. }
  263. const auto resultWidth = qrMaxSize
  264. + rect::m::sum::h(state->backgroundMargins);
  265. {
  266. aboutLabel->setText(about);
  267. aboutLabel->resizeToWidth(resultWidth);
  268. }
  269. const auto textMaxWidth = state->backgroundMargins.left()
  270. + (state->qrImage.width() / style::DevicePixelRatio());
  271. const auto lines = int(state->textWidth / textMaxWidth) + 1;
  272. state->textMaxHeight = state->textWidth
  273. ? (state->font->height * lines)
  274. : 0;
  275. const auto whiteMargins = RoundedMargins(
  276. state->backgroundMargins,
  277. state->photoSize,
  278. state->textMaxHeight);
  279. result->resize(
  280. qrMaxSize + rect::m::sum::h(whiteMargins),
  281. qrMaxSize
  282. + rect::m::sum::v(whiteMargins) // White.
  283. + rect::m::sum::v(st::profileQrBackgroundPadding) // Gray.
  284. + state->photoSize / 2
  285. + aboutLabel->height());
  286. divider->resize(container->width(), result->height());
  287. result->moveToLeft((container->width() - result->width()) / 2, 0);
  288. topWidget->setVisible(userpicToggled);
  289. topWidget->moveToLeft(0, std::numeric_limits<int>::min());
  290. topWidget->raise();
  291. aboutLabel->raise();
  292. aboutLabel->moveToLeft(
  293. result->x(),
  294. divider->height()
  295. - aboutLabel->height()
  296. - st::defaultBoxDividerLabelPadding.top());
  297. }, container->lifetime());
  298. result->paintRequest(
  299. ) | rpl::start_with_next([=](QRect clip) {
  300. auto p = QPainter(result);
  301. const auto size = (state->qrImage.size() / style::DevicePixelRatio());
  302. const auto qrRect = Rect(
  303. (result->width() - size.width()) / 2,
  304. state->backgroundMargins.top() + state->photoSize / 2,
  305. size);
  306. p.translate(
  307. 0,
  308. st::profileQrBackgroundPadding.top() + state->photoSize / 2);
  309. Paint(
  310. p,
  311. state->font,
  312. state->text,
  313. state->backgroundColors,
  314. state->backgroundMargins,
  315. state->qrImage,
  316. qrRect,
  317. qrMaxSize,
  318. st::introQrPixel,
  319. st::profileQrBackgroundRadius,
  320. state->textMaxHeight,
  321. state->photoSize,
  322. state->backgroundToggled);
  323. if (!state->photoSize) {
  324. return;
  325. }
  326. const auto photoSize = state->photoSize;
  327. const auto top = Ui::GrabWidget(
  328. topWidget,
  329. QRect(),
  330. Qt::transparent).scaled(
  331. Size(photoSize * style::DevicePixelRatio()),
  332. Qt::IgnoreAspectRatio,
  333. Qt::SmoothTransformation);
  334. p.drawPixmap((result->width() - photoSize) / 2, -photoSize / 2, top);
  335. }, result->lifetime());
  336. return result;
  337. }
  338. [[nodiscard]] Fn<void(int)> AddDotsToSlider(
  339. not_null<Ui::ContinuousSlider*> slider,
  340. const style::MediaSlider &st,
  341. int count) {
  342. const auto lineWidth = st::lineWidth;
  343. const auto smallSize = Size(st.seekSize.height() - st.width);
  344. auto smallDots = std::vector<not_null<Ui::RpWidget*>>();
  345. smallDots.reserve(count - 1);
  346. const auto paintSmall = [=](QPainter &p, const QBrush &brush) {
  347. auto hq = PainterHighQualityEnabler(p);
  348. auto pen = st::boxBg->p;
  349. pen.setWidth(st.width);
  350. p.setPen(pen);
  351. p.setBrush(brush);
  352. p.drawEllipse(Rect(smallSize) - Margins(lineWidth));
  353. };
  354. for (auto i = 0; i < count - 1; i++) {
  355. smallDots.push_back(
  356. Ui::CreateChild<Ui::RpWidget>(slider->parentWidget()));
  357. const auto dot = smallDots.back();
  358. dot->resize(smallSize);
  359. dot->setAttribute(Qt::WA_TransparentForMouseEvents);
  360. dot->paintRequest() | rpl::start_with_next([=] {
  361. auto p = QPainter(dot);
  362. const auto fg = (slider->value() > (i / float64(count - 1)))
  363. ? st.activeFg
  364. : st.inactiveFg;
  365. paintSmall(p, fg);
  366. }, dot->lifetime());
  367. }
  368. const auto bigDot = Ui::CreateChild<Ui::RpWidget>(slider->parentWidget());
  369. bigDot->resize(st.seekSize);
  370. bigDot->setAttribute(Qt::WA_TransparentForMouseEvents);
  371. bigDot->paintRequest() | rpl::start_with_next([=] {
  372. auto p = QPainter(bigDot);
  373. auto hq = PainterHighQualityEnabler(p);
  374. auto pen = st::boxBg->p;
  375. pen.setWidth(st.width);
  376. p.setPen(pen);
  377. p.setBrush(st.activeFg);
  378. p.drawEllipse(Rect(st.seekSize) - Margins(lineWidth));
  379. }, bigDot->lifetime());
  380. return [=](int index) {
  381. const auto g = slider->geometry();
  382. const auto bigTop = g.y() + (g.height() - bigDot->height()) / 2;
  383. const auto smallTop = g.y()
  384. + (g.height() - smallSize.height()) / 2;
  385. for (auto i = 0; i < count; ++i) {
  386. if (index == i) {
  387. const auto x = ((g.width() - bigDot->width()) * i)
  388. / float64(count - 1);
  389. bigDot->move(g.x() + std::round(x), bigTop);
  390. } else {
  391. const auto k = (i < index) ? i : i - 1;
  392. const auto w = smallDots[k]->width();
  393. smallDots[k]->move(
  394. g.x() + ((g.width() - w) * i) / (count - 1),
  395. smallTop);
  396. }
  397. }
  398. };
  399. }
  400. } // namespace
  401. void FillPeerQrBox(
  402. not_null<Ui::GenericBox*> box,
  403. PeerData *peer,
  404. std::optional<QString> customLink,
  405. rpl::producer<QString> about) {
  406. const auto window = Core::App().findWindow(box);
  407. const auto controller = window ? window->sessionController() : nullptr;
  408. if (!controller) {
  409. return;
  410. }
  411. box->setStyle(st::giveawayGiftCodeBox);
  412. box->setNoContentMargin(true);
  413. box->setWidth(st::aboutWidth);
  414. box->setTitle(tr::lng_group_invite_context_qr());
  415. box->verticalLayout()->resizeToWidth(box->width());
  416. struct State {
  417. Ui::RpWidget* saveButton = nullptr;
  418. rpl::variable<bool> saveButtonBusy = false;
  419. rpl::variable<bool> userpicToggled = true;
  420. rpl::variable<bool> backgroundToggled = true;
  421. rpl::variable<Colors> bgs;
  422. Ui::Animations::Simple animation;
  423. rpl::variable<int> chosen = 0;
  424. rpl::variable<int> scaleValue = 0;
  425. rpl::variable<int> fontSizeValue = 28;
  426. };
  427. const auto state = box->lifetime().make_state<State>();
  428. state->userpicToggled = !(customLink || !peer);
  429. const auto usernameValue = [=] {
  430. return (customLink || !peer)
  431. ? (rpl::single(QString()) | rpl::type_erased())
  432. : Info::Profile::UsernameValue(peer, true) | rpl::map(
  433. [](const auto &username) { return username.text; });
  434. };
  435. const auto linkValue = [=] {
  436. return customLink
  437. ? rpl::single(*customLink)
  438. : peer
  439. ? Info::Profile::LinkValue(peer, true) | rpl::map(
  440. [](const auto &link) { return link.text; })
  441. : (rpl::single(QString()) | rpl::type_erased());
  442. };
  443. const auto userpic = Ui::CreateChild<Ui::RpWidget>(box);
  444. const auto userpicSize = st::defaultUserpicButton.photoSize;
  445. userpic->resize(Size(userpicSize));
  446. const auto userpicMedia = Ui::MakeUserpicThumbnail(peer
  447. ? peer
  448. : controller->session().user().get());
  449. userpicMedia->subscribeToUpdates([=] { userpic->update(); });
  450. userpic->paintRequest() | rpl::start_with_next([=] {
  451. auto p = QPainter(userpic);
  452. p.drawImage(0, 0, userpicMedia->image(userpicSize));
  453. }, userpic->lifetime());
  454. userpic->setVisible(peer != nullptr);
  455. PrepareQrWidget(
  456. box->verticalLayout(),
  457. userpic,
  458. state->fontSizeValue.value(),
  459. state->userpicToggled.value(),
  460. state->backgroundToggled.value(),
  461. usernameValue(),
  462. linkValue(),
  463. state->bgs.value(),
  464. about ? std::move(about) : rpl::single(QString()));
  465. Ui::AddSkip(box->verticalLayout());
  466. Ui::AddSubsectionTitle(
  467. box->verticalLayout(),
  468. tr::lng_userpic_builder_color_subtitle());
  469. const auto themesContainer = box->addRow(
  470. object_ptr<Ui::VerticalLayout>(box));
  471. const auto activewidth = int(
  472. (st::defaultInputField.borderActive + st::lineWidth) * 0.9);
  473. const auto size = st::chatThemePreviewSize.width();
  474. const auto fill = [=](const std::vector<Data::CloudTheme> &cloudThemes) {
  475. while (themesContainer->count()) {
  476. delete themesContainer->widgetAt(0);
  477. }
  478. struct State {
  479. Colors colors;
  480. QImage image;
  481. };
  482. constexpr auto kMaxInRow = 4;
  483. constexpr auto kMaxColors = 4;
  484. auto row = (Ui::RpWidget*)(nullptr);
  485. auto counter = 0;
  486. const auto spacing = (0
  487. + (box->width() - rect::m::sum::h(st::boxRowPadding))
  488. - (kMaxInRow * size)) / (kMaxInRow + 1);
  489. auto colorsCollection = ranges::views::all(
  490. cloudThemes
  491. ) | ranges::views::transform([](const auto &cloudTheme) -> Colors {
  492. const auto it = cloudTheme.settings.find(
  493. Data::CloudThemeType::Light);
  494. if (it == end(cloudTheme.settings)) {
  495. return Colors();
  496. }
  497. const auto colors = it->second.paper
  498. ? it->second.paper->backgroundColors()
  499. : Colors();
  500. if (colors.size() != kMaxColors) {
  501. return Colors();
  502. }
  503. return colors;
  504. }) | ranges::views::filter([](const Colors &colors) {
  505. return !colors.empty();
  506. }) | ranges::to_vector;
  507. Expects(!colorsCollection.empty());
  508. colorsCollection[0] = Colors{
  509. st::premiumButtonBg1->c,
  510. st::premiumButtonBg1->c,
  511. st::premiumButtonBg2->c,
  512. st::premiumButtonBg3->c,
  513. };
  514. // colorsCollection.push_back(Colors{
  515. // st::creditsBg1->c,
  516. // st::creditsBg2->c,
  517. // st::creditsBg1->c,
  518. // st::creditsBg2->c,
  519. // });
  520. for (const auto &colors : colorsCollection) {
  521. if (state->bgs.current().empty()) {
  522. state->bgs = colors;
  523. }
  524. if (counter % kMaxInRow == 0) {
  525. Ui::AddSkip(themesContainer);
  526. row = themesContainer->add(
  527. object_ptr<Ui::RpWidget>(themesContainer));
  528. row->resize(size, size);
  529. }
  530. const auto widget = Ui::CreateChild<Ui::AbstractButton>(row);
  531. widget->setClickedCallback([=] {
  532. state->chosen = counter;
  533. widget->update();
  534. state->animation.stop();
  535. state->animation.start([=](float64 value) {
  536. const auto was = state->bgs.current();
  537. const auto &now = colors;
  538. if (was.size() == now.size()
  539. && was.size() == kMaxColors) {
  540. state->bgs = Colors({
  541. anim::color(was[0], now[0], value),
  542. anim::color(was[1], now[1], value),
  543. anim::color(was[2], now[2], value),
  544. anim::color(was[3], now[3], value),
  545. });
  546. }
  547. },
  548. 0.,
  549. 1.,
  550. st::shakeDuration);
  551. });
  552. state->chosen.value() | rpl::combine_previous(
  553. ) | rpl::filter([=](int i, int k) {
  554. return i == counter || k == counter;
  555. }) | rpl::start_with_next([=] {
  556. widget->update();
  557. }, widget->lifetime());
  558. widget->resize(size, size);
  559. widget->moveToLeft(
  560. spacing + ((counter % kMaxInRow) * (size + spacing)),
  561. 0);
  562. widget->show();
  563. const auto back = [&] {
  564. auto result = Images::Round(
  565. Images::GenerateGradient(
  566. Size(size - activewidth * 5),
  567. colors,
  568. 0,
  569. 0),
  570. ImageRoundRadius::Large);
  571. auto colored = result;
  572. colored.fill(Qt::transparent);
  573. {
  574. auto p = QPainter(&colored);
  575. auto hq = PainterHighQualityEnabler(p);
  576. st::profileQrIcon.paintInCenter(p, result.rect());
  577. p.setCompositionMode(QPainter::CompositionMode_SourceIn);
  578. p.drawImage(0, 0, result);
  579. }
  580. auto temp = result;
  581. temp.fill(Qt::transparent);
  582. {
  583. auto p = QPainter(&temp);
  584. auto hq = PainterHighQualityEnabler(p);
  585. p.setPen(st::premiumButtonFg);
  586. p.setBrush(st::premiumButtonFg);
  587. const auto size = st::profileQrIcon.width() * 1.5;
  588. const auto margins = Margins((result.width() - size) / 2);
  589. const auto inner = result.rect() - margins;
  590. p.drawRoundedRect(
  591. inner,
  592. st::roundRadiusLarge,
  593. st::roundRadiusLarge);
  594. p.drawImage(0, 0, colored);
  595. }
  596. {
  597. auto p = QPainter(&result);
  598. p.drawImage(0, 0, temp);
  599. }
  600. return result;
  601. }();
  602. widget->paintRequest() | rpl::start_with_next([=] {
  603. auto p = QPainter(widget);
  604. const auto rect = widget->rect() - Margins(activewidth * 2.5);
  605. p.drawImage(rect.x(), rect.y(), back);
  606. if (state->chosen.current() == counter) {
  607. auto hq = PainterHighQualityEnabler(p);
  608. auto pen = st::activeLineFg->p;
  609. pen.setWidth(st::defaultInputField.borderActive);
  610. p.setPen(pen);
  611. const auto r = st::roundRadiusLarge
  612. + activewidth * 2.1 * style::DevicePixelRatio();
  613. p.drawRoundedRect(
  614. widget->rect() - Margins(pen.width()),
  615. r,
  616. r);
  617. }
  618. }, widget->lifetime());
  619. counter++;
  620. }
  621. Ui::AddSkip(themesContainer);
  622. Ui::AddSkip(themesContainer);
  623. themesContainer->resizeToWidth(box->width());
  624. };
  625. const auto themes = &controller->session().data().cloudThemes();
  626. const auto &list = themes->chatThemes();
  627. if (!list.empty()) {
  628. fill(list);
  629. } else {
  630. themes->refreshChatThemes();
  631. themes->chatThemesUpdated(
  632. ) | rpl::take(1) | rpl::start_with_next([=] {
  633. fill(themes->chatThemes());
  634. }, box->lifetime());
  635. }
  636. Ui::AddSkip(box->verticalLayout());
  637. Ui::AddDivider(box->verticalLayout());
  638. Ui::AddSkip(box->verticalLayout());
  639. Ui::AddSubsectionTitle(
  640. box->verticalLayout(),
  641. tr::lng_qr_box_quality());
  642. Ui::AddSkip(box->verticalLayout());
  643. constexpr auto kMaxQualities = 3;
  644. {
  645. const auto seekSize = st::settingsScale.seekSize.height();
  646. const auto &labelSt = st::defaultFlatLabel;
  647. const auto labels = box->verticalLayout()->add(
  648. Ui::CreateSkipWidget(
  649. box,
  650. labelSt.style.font->height + labelSt.style.font->descent),
  651. st::boxRowPadding);
  652. const auto left = Ui::CreateChild<Ui::FlatLabel>(
  653. labels,
  654. tr::lng_qr_box_quality1(),
  655. labelSt);
  656. const auto middle = Ui::CreateChild<Ui::FlatLabel>(
  657. labels,
  658. tr::lng_qr_box_quality2(),
  659. labelSt);
  660. const auto right = Ui::CreateChild<Ui::FlatLabel>(
  661. labels,
  662. tr::lng_qr_box_quality3(),
  663. labelSt);
  664. labels->sizeValue(
  665. ) | rpl::start_with_next([=](const QSize &size) {
  666. left->moveToLeft(0, 0);
  667. middle->moveToLeft((size.width() - middle->width()) / 2, 0);
  668. right->moveToRight(0, 0);
  669. }, labels->lifetime());
  670. const auto slider = box->verticalLayout()->add(
  671. object_ptr<Ui::MediaSliderWheelless>(
  672. box->verticalLayout(),
  673. st::settingsScale),
  674. st::boxRowPadding);
  675. slider->resize(slider->width(), seekSize);
  676. const auto active = st::windowActiveTextFg->c;
  677. const auto inactive = st::windowSubTextFg->c;
  678. const auto colorize = [=](int index) {
  679. if (index == 0) {
  680. left->setTextColorOverride(active);
  681. middle->setTextColorOverride(inactive);
  682. right->setTextColorOverride(inactive);
  683. } else if (index == 1) {
  684. left->setTextColorOverride(inactive);
  685. middle->setTextColorOverride(active);
  686. right->setTextColorOverride(inactive);
  687. } else if (index == 2) {
  688. left->setTextColorOverride(inactive);
  689. middle->setTextColorOverride(inactive);
  690. right->setTextColorOverride(active);
  691. }
  692. };
  693. const auto updateGeometry = AddDotsToSlider(
  694. slider,
  695. st::settingsScale,
  696. kMaxQualities);
  697. slider->geometryValue(
  698. ) | rpl::start_with_next([=](const QRect &rect) {
  699. updateGeometry(int(slider->value() * (kMaxQualities - 1)));
  700. }, box->lifetime());
  701. box->setShowFinishedCallback([=] {
  702. colorize(0);
  703. updateGeometry(0);
  704. });
  705. slider->setPseudoDiscrete(
  706. kMaxQualities,
  707. [=](int index) { return index; },
  708. 0,
  709. [=](int scale) {
  710. state->scaleValue = scale;
  711. colorize(scale);
  712. updateGeometry(scale);
  713. },
  714. [](int) {});
  715. }
  716. {
  717. Ui::AddSkip(box->verticalLayout());
  718. Ui::AddSkip(box->verticalLayout());
  719. Ui::AddSubsectionTitle(
  720. box->verticalLayout(),
  721. tr::lng_qr_box_font_size());
  722. Ui::AddSkip(box->verticalLayout());
  723. const auto seekSize = st::settingsScale.seekSize.height();
  724. const auto slider = box->verticalLayout()->add(
  725. object_ptr<Ui::MediaSliderWheelless>(
  726. box->verticalLayout(),
  727. st::settingsScale),
  728. st::boxRowPadding);
  729. slider->resize(slider->width(), seekSize);
  730. const auto kSizeAmount = 8;
  731. const auto kMinSize = 20;
  732. const auto kMaxSize = 36;
  733. const auto kStep = (kMaxSize - kMinSize) / (kSizeAmount - 1);
  734. const auto updateGeometry = AddDotsToSlider(
  735. slider,
  736. st::settingsScale,
  737. kSizeAmount);
  738. const auto fontSizeToIndex = [=](int fontSize) {
  739. return (fontSize - kMinSize) / kStep;
  740. };
  741. const auto indexToFontSize = [=](int index) {
  742. return kMinSize + index * kStep;
  743. };
  744. slider->geometryValue(
  745. ) | rpl::start_with_next([=](const QRect &rect) {
  746. updateGeometry(fontSizeToIndex(state->fontSizeValue.current()));
  747. }, box->lifetime());
  748. box->setShowFinishedCallback([=] {
  749. updateGeometry(fontSizeToIndex(state->fontSizeValue.current()));
  750. });
  751. slider->setPseudoDiscrete(
  752. kSizeAmount,
  753. [=](int index) { return indexToFontSize(index); },
  754. state->fontSizeValue.current(),
  755. [=](int fontSize) {
  756. state->fontSizeValue = fontSize;
  757. updateGeometry(fontSizeToIndex(fontSize));
  758. },
  759. [](int) {});
  760. }
  761. Ui::AddSkip(box->verticalLayout());
  762. Ui::AddSkip(box->verticalLayout());
  763. if (peer) {
  764. const auto userpicToggle = box->verticalLayout()->add(
  765. object_ptr<Ui::SettingsButton>(
  766. box->verticalLayout(),
  767. (peer->isUser()
  768. ? tr::lng_mediaview_profile_photo
  769. : (peer->isChannel() && !peer->isMegagroup())
  770. ? tr::lng_mediaview_channel_photo
  771. : tr::lng_mediaview_group_photo)(),
  772. st::settingsButtonNoIcon));
  773. userpicToggle->toggleOn(state->userpicToggled.value(), true);
  774. userpicToggle->setClickedCallback([=] {
  775. state->userpicToggled = !state->userpicToggled.current();
  776. });
  777. }
  778. {
  779. const auto backgroundToggle = box->verticalLayout()->add(
  780. object_ptr<Ui::SettingsButton>(
  781. box->verticalLayout(),
  782. tr::lng_qr_box_transparent_background(),
  783. st::settingsButtonNoIcon));
  784. backgroundToggle->toggleOn(
  785. state->backgroundToggled.value() | rpl::map(!rpl::mappers::_1),
  786. true);
  787. backgroundToggle->setClickedCallback([=] {
  788. state->backgroundToggled = !state->backgroundToggled.current();
  789. });
  790. }
  791. Ui::AddSkip(box->verticalLayout());
  792. Ui::AddSkip(box->verticalLayout());
  793. auto buttonText = rpl::conditional(
  794. state->saveButtonBusy.value() | rpl::map(rpl::mappers::_1),
  795. rpl::single(QString()),
  796. tr::lng_chat_link_copy());
  797. const auto show = controller->uiShow();
  798. state->saveButton = box->addButton(std::move(buttonText), [=] {
  799. if (state->saveButtonBusy.current()) {
  800. return;
  801. }
  802. const auto buttonWidth = state->saveButton
  803. ? state->saveButton->width()
  804. : 0;
  805. state->saveButtonBusy = true;
  806. if (state->saveButton) {
  807. state->saveButton->resizeToWidth(buttonWidth);
  808. }
  809. const auto userpicToggled = state->userpicToggled.current();
  810. const auto backgroundToggled = state->backgroundToggled.current();
  811. const auto scale = style::kScaleDefault
  812. * (kMaxQualities + int(state->scaleValue.current() * 2));
  813. const auto divider = std::max(100, style::Scale())
  814. / style::kScaleDefault;
  815. const auto profileQrBackgroundRadius = style::ConvertScale(
  816. st::profileQrBackgroundRadius / divider,
  817. scale);
  818. const auto introQrPixel = style::ConvertScale(
  819. st::introQrPixel / divider,
  820. scale);
  821. const auto lineWidth = style::ConvertScale(
  822. st::lineWidth / divider,
  823. scale);
  824. const auto boxWideWidth = style::ConvertScale(
  825. st::boxWideWidth / divider,
  826. scale);
  827. const auto createMargins = [&](const style::margins &margins) {
  828. return QMargins(
  829. style::ConvertScale(margins.left() / divider, scale),
  830. style::ConvertScale(margins.top() / divider, scale),
  831. style::ConvertScale(margins.right() / divider, scale),
  832. style::ConvertScale(margins.bottom() / divider, scale));
  833. };
  834. const auto boxRowPadding = createMargins(st::boxRowPadding);
  835. const auto backgroundMargins = userpicToggled
  836. ? createMargins(st::profileQrBackgroundMargins)
  837. : createMargins(NoPhotoBackgroundMargins());
  838. const auto qrMaxSize = boxWideWidth
  839. - rect::m::sum::h(boxRowPadding)
  840. - rect::m::sum::h(backgroundMargins);
  841. const auto photoSize = userpicToggled
  842. ? style::ConvertScale(
  843. st::defaultUserpicButton.photoSize / divider,
  844. scale)
  845. : 0;
  846. const auto font = CreateFont(state->fontSizeValue.current(), scale);
  847. const auto username = rpl::variable<QString>(
  848. usernameValue()).current().toUpper();
  849. const auto link = rpl::variable<QString>(linkValue());
  850. const auto textWidth = font->width(username);
  851. const auto top = photoSize
  852. ? userpicMedia->image(photoSize)
  853. : QImage();
  854. const auto weak = Ui::MakeWeak(box);
  855. crl::async([=] {
  856. const auto qrImage = TelegramQr(
  857. Qr::Encode(
  858. link.current().toUtf8(),
  859. Qr::Redundancy::Default),
  860. introQrPixel,
  861. qrMaxSize,
  862. backgroundToggled);
  863. const auto textMaxWidth = backgroundMargins.left()
  864. + (qrImage.width() / style::DevicePixelRatio());
  865. const auto lines = int(textWidth / textMaxWidth) + 1;
  866. const auto textMaxHeight = textWidth ? font->height * lines : 0;
  867. const auto whiteMargins = RoundedMargins(
  868. backgroundMargins,
  869. photoSize,
  870. textMaxHeight);
  871. const auto resultSize = QSize(
  872. qrMaxSize + rect::m::sum::h(whiteMargins),
  873. qrMaxSize + rect::m::sum::v(whiteMargins) + photoSize / 2);
  874. const auto qrImageSize = qrImage.size()
  875. / style::DevicePixelRatio();
  876. const auto qrRect = Rect(
  877. (resultSize.width() - qrImageSize.width()) / 2,
  878. whiteMargins.top() + photoSize / 2,
  879. qrImageSize);
  880. auto image = QImage(
  881. resultSize * style::DevicePixelRatio(),
  882. QImage::Format_ARGB32_Premultiplied);
  883. image.fill(Qt::transparent);
  884. image.setDevicePixelRatio(style::DevicePixelRatio());
  885. {
  886. auto p = QPainter(&image);
  887. p.translate(0, lineWidth); // Bad.
  888. Paint(
  889. p,
  890. font,
  891. username,
  892. state->bgs.current(),
  893. backgroundMargins,
  894. qrImage,
  895. qrRect,
  896. qrMaxSize,
  897. introQrPixel,
  898. profileQrBackgroundRadius,
  899. textMaxHeight,
  900. photoSize,
  901. backgroundToggled);
  902. if (userpicToggled) {
  903. p.drawImage((resultSize.width() - photoSize) / 2, 0, top);
  904. }
  905. }
  906. crl::on_main(weak, [=] {
  907. state->saveButtonBusy = false;
  908. auto mime = std::make_unique<QMimeData>();
  909. mime->setImageData(std::move(image));
  910. QGuiApplication::clipboard()->setMimeData(mime.release());
  911. show->showToast(tr::lng_group_invite_qr_copied(tr::now));
  912. });
  913. });
  914. });
  915. if (const auto saveButton = state->saveButton) {
  916. using namespace Info::Statistics;
  917. const auto loadingAnimation = InfiniteRadialAnimationWidget(
  918. saveButton,
  919. saveButton->height() / 2);
  920. AddChildToWidgetCenter(saveButton, loadingAnimation);
  921. loadingAnimation->showOn(state->saveButtonBusy.value());
  922. }
  923. const auto buttonWidth = box->width()
  924. - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding);
  925. state->saveButton->widthValue() | rpl::filter([=] {
  926. return (state->saveButton->widthNoMargins() != buttonWidth);
  927. }) | rpl::start_with_next([=] {
  928. state->saveButton->resizeToWidth(buttonWidth);
  929. }, state->saveButton->lifetime());
  930. box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); });
  931. }
  932. } // namespace Ui