empty_userpic.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  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/empty_userpic.h"
  8. #include "info/channel_statistics/earn/earn_icons.h"
  9. #include "ui/chat/chat_style.h"
  10. #include "ui/effects/animation_value.h"
  11. #include "ui/emoji_config.h"
  12. #include "ui/painter.h"
  13. #include "ui/ui_utility.h"
  14. #include "styles/style_chat.h"
  15. #include "styles/style_dialogs.h"
  16. #include "styles/style_widgets.h" // style::IconButton
  17. #include "styles/style_info.h" // st::topBarCall
  18. #include <QtSvg/QSvgRenderer>
  19. namespace Ui {
  20. namespace {
  21. [[nodiscard]] bool IsExternal(const QString &name) {
  22. return !name.isEmpty()
  23. && (name.front() == QChar(0))
  24. && QStringView(name).mid(1) == u"external"_q;
  25. }
  26. [[nodiscard]] bool IsInaccessible(const QString &name) {
  27. return !name.isEmpty()
  28. && (name.front() == QChar(0))
  29. && QStringView(name).mid(1) == u"inaccessible"_q;
  30. }
  31. void PaintSavedMessagesInner(
  32. QPainter &p,
  33. int x,
  34. int y,
  35. int size,
  36. const style::color &fg) {
  37. // |<----width----->|
  38. //
  39. // XXXXXXXXXXXXXXXXXX ---
  40. // X X |
  41. // X X |
  42. // X X |
  43. // X X height
  44. // X XX X | ---
  45. // X XX XX X | |
  46. // X XX XX X | add
  47. // X XX XX X | |
  48. // XX XX --- ---
  49. const auto thinkness = base::SafeRound(size * 0.055);
  50. const auto increment = int(thinkness) % 2 + (size % 2);
  51. const auto width = base::SafeRound(size * 0.15) * 2 + increment;
  52. const auto height = base::SafeRound(size * 0.19) * 2 + increment;
  53. const auto add = base::SafeRound(size * 0.064);
  54. const auto left = x + (size - width) / 2;
  55. const auto top = y + (size - height) / 2;
  56. const auto right = left + width;
  57. const auto bottom = top + height;
  58. const auto middle = (left + right) / 2;
  59. const auto half = (top + bottom) / 2;
  60. p.setBrush(Qt::NoBrush);
  61. auto pen = fg->p;
  62. pen.setWidthF(thinkness);
  63. pen.setCapStyle(Qt::FlatCap);
  64. {
  65. // XXXXXXXXXXXXXXXXXX
  66. // X X
  67. // X X
  68. // X X
  69. // X X
  70. // X X
  71. pen.setJoinStyle(Qt::RoundJoin);
  72. p.setPen(pen);
  73. QPainterPath path;
  74. path.moveTo(left, half);
  75. path.lineTo(left, top);
  76. path.lineTo(right, top);
  77. path.lineTo(right, half);
  78. p.drawPath(path);
  79. }
  80. {
  81. // X X
  82. // X XX X
  83. // X XX XX X
  84. // X XX XX X
  85. // X XX XX X
  86. // XX XX
  87. pen.setJoinStyle(Qt::MiterJoin);
  88. p.setPen(pen);
  89. QPainterPath path;
  90. path.moveTo(left, half);
  91. path.lineTo(left, bottom);
  92. path.lineTo(middle, bottom - add);
  93. path.lineTo(right, bottom);
  94. path.lineTo(right, half);
  95. p.drawPath(path);
  96. }
  97. }
  98. void PaintIconInner(
  99. QPainter &p,
  100. int x,
  101. int y,
  102. int size,
  103. int defaultSize,
  104. const style::icon &icon,
  105. const style::color &fg) {
  106. if (size == defaultSize) {
  107. const auto rect = QRect{ x, y, size, size };
  108. icon.paintInCenter(
  109. p,
  110. rect,
  111. fg->c);
  112. } else {
  113. p.save();
  114. const auto ratio = size / float64(defaultSize);
  115. p.translate(x + size / 2., y + size / 2.);
  116. p.scale(ratio, ratio);
  117. const auto skip = defaultSize;
  118. const auto rect = QRect{ -skip, -skip, 2 * skip, 2 * skip };
  119. icon.paintInCenter(
  120. p,
  121. rect,
  122. fg->c);
  123. p.restore();
  124. }
  125. }
  126. void PaintRepliesMessagesInner(
  127. QPainter &p,
  128. int x,
  129. int y,
  130. int size,
  131. const style::color &fg) {
  132. PaintIconInner(
  133. p,
  134. x,
  135. y,
  136. size,
  137. st::defaultDialogRow.photoSize,
  138. st::dialogsRepliesUserpic,
  139. fg);
  140. }
  141. void PaintHiddenAuthorInner(
  142. QPainter &p,
  143. int x,
  144. int y,
  145. int size,
  146. const style::color &fg) {
  147. PaintIconInner(
  148. p,
  149. x,
  150. y,
  151. size,
  152. st::defaultDialogRow.photoSize,
  153. st::dialogsHiddenAuthorUserpic,
  154. fg);
  155. }
  156. void PaintMyNotesInner(
  157. QPainter &p,
  158. int x,
  159. int y,
  160. int size,
  161. const style::color &fg) {
  162. PaintIconInner(
  163. p,
  164. x,
  165. y,
  166. size,
  167. st::defaultDialogRow.photoSize,
  168. st::dialogsMyNotesUserpic,
  169. fg);
  170. }
  171. void PaintCurrencyInner(
  172. QPainter &p,
  173. int x,
  174. int y,
  175. int size,
  176. const style::color &fg) {
  177. auto svg = QSvgRenderer(Ui::Earn::CurrencySvgColored(fg->c));
  178. const auto skip = size / 5;
  179. svg.render(&p, QRect(x, y, size, size).marginsRemoved(
  180. { skip, skip, skip, skip }));
  181. }
  182. void PaintExternalMessagesInner(
  183. QPainter &p,
  184. int x,
  185. int y,
  186. int size,
  187. const style::color &fg) {
  188. PaintIconInner(
  189. p,
  190. x,
  191. y,
  192. size,
  193. st::msgPhotoSize,
  194. st::topBarCall.icon,
  195. fg);
  196. }
  197. void PaintInaccessibleAccountInner(
  198. QPainter &p,
  199. int x,
  200. int y,
  201. int size,
  202. const style::color &fg) {
  203. if (size > st::defaultDialogRow.photoSize) {
  204. PaintIconInner(
  205. p,
  206. x,
  207. y,
  208. size,
  209. st::infoProfilePhotoInnerSize,
  210. st::infoProfileInaccessibleUserpic,
  211. fg);
  212. } else {
  213. PaintIconInner(
  214. p,
  215. x,
  216. y,
  217. size,
  218. st::defaultDialogRow.photoSize,
  219. st::dialogsInaccessibleUserpic,
  220. fg);
  221. }
  222. }
  223. [[nodiscard]] QImage Generate(int size, Fn<void(QPainter&)> callback) {
  224. auto result = QImage(
  225. QSize(size, size) * style::DevicePixelRatio(),
  226. QImage::Format_ARGB32_Premultiplied);
  227. result.setDevicePixelRatio(style::DevicePixelRatio());
  228. result.fill(Qt::transparent);
  229. {
  230. Painter p(&result);
  231. callback(p);
  232. }
  233. return result;
  234. }
  235. } // namespace
  236. EmptyUserpic::EmptyUserpic(const BgColors &colors, const QString &name)
  237. : _colors(colors) {
  238. fillString(name);
  239. }
  240. QString EmptyUserpic::ExternalName() {
  241. return QChar(0) + u"external"_q;
  242. }
  243. QString EmptyUserpic::InaccessibleName() {
  244. return QChar(0) + u"inaccessible"_q;
  245. }
  246. uint8 EmptyUserpic::ColorIndex(uint64 id) {
  247. return DecideColorIndex(id);
  248. }
  249. EmptyUserpic::BgColors EmptyUserpic::UserpicColor(uint8 colorIndex) {
  250. const EmptyUserpic::BgColors colors[] = {
  251. { st::historyPeer1UserpicBg, st::historyPeer1UserpicBg2 },
  252. { st::historyPeer2UserpicBg, st::historyPeer2UserpicBg2 },
  253. { st::historyPeer3UserpicBg, st::historyPeer3UserpicBg2 },
  254. { st::historyPeer4UserpicBg, st::historyPeer4UserpicBg2 },
  255. { st::historyPeer5UserpicBg, st::historyPeer5UserpicBg2 },
  256. { st::historyPeer6UserpicBg, st::historyPeer6UserpicBg2 },
  257. { st::historyPeer7UserpicBg, st::historyPeer7UserpicBg2 },
  258. { st::historyPeer8UserpicBg, st::historyPeer8UserpicBg2 },
  259. };
  260. return colors[ColorIndexToPaletteIndex(colorIndex)];
  261. }
  262. void EmptyUserpic::paint(
  263. QPainter &p,
  264. int x,
  265. int y,
  266. int outerWidth,
  267. int size,
  268. Fn<void()> paintBackground) const {
  269. x = style::RightToLeft() ? (outerWidth - x - size) : x;
  270. const auto fontsize = (size * 13) / 33;
  271. auto font = st::historyPeerUserpicFont->f;
  272. font.setPixelSize(fontsize);
  273. PainterHighQualityEnabler hq(p);
  274. {
  275. auto gradient = QLinearGradient(x, y, x, y + size);
  276. gradient.setStops({
  277. { 0., _colors.color1->c },
  278. { 1., _colors.color2->c }
  279. });
  280. p.setBrush(gradient);
  281. }
  282. p.setPen(Qt::NoPen);
  283. paintBackground();
  284. if (IsExternal(_string)) {
  285. PaintExternalMessagesInner(p, x, y, size, st::historyPeerUserpicFg);
  286. } else if (IsInaccessible(_string)) {
  287. PaintInaccessibleAccountInner(
  288. p,
  289. x,
  290. y,
  291. size,
  292. st::historyPeerUserpicFg);
  293. } else {
  294. p.setFont(font);
  295. p.setBrush(Qt::NoBrush);
  296. p.setPen(st::historyPeerUserpicFg);
  297. p.drawText(
  298. QRect(x, y, size, size),
  299. _string,
  300. QTextOption(style::al_center));
  301. }
  302. }
  303. void EmptyUserpic::paintCircle(
  304. QPainter &p,
  305. int x,
  306. int y,
  307. int outerWidth,
  308. int size) const {
  309. paint(p, x, y, outerWidth, size, [&] {
  310. p.drawEllipse(x, y, size, size);
  311. });
  312. }
  313. void EmptyUserpic::paintRounded(
  314. QPainter &p,
  315. int x,
  316. int y,
  317. int outerWidth,
  318. int size,
  319. int radius) const {
  320. paint(p, x, y, outerWidth, size, [&] {
  321. p.drawRoundedRect(x, y, size, size, radius, radius);
  322. });
  323. }
  324. void EmptyUserpic::paintSquare(
  325. QPainter &p,
  326. int x,
  327. int y,
  328. int outerWidth,
  329. int size) const {
  330. paint(p, x, y, outerWidth, size, [&] {
  331. p.fillRect(x, y, size, size, p.brush());
  332. });
  333. }
  334. void EmptyUserpic::PaintSavedMessages(
  335. QPainter &p,
  336. int x,
  337. int y,
  338. int outerWidth,
  339. int size) {
  340. auto bg = QLinearGradient(x, y, x, y + size);
  341. bg.setStops({
  342. { 0., st::historyPeerSavedMessagesBg->c },
  343. { 1., st::historyPeerSavedMessagesBg2->c }
  344. });
  345. const auto &fg = st::historyPeerUserpicFg;
  346. PaintSavedMessages(p, x, y, outerWidth, size, QBrush(bg), fg);
  347. }
  348. void EmptyUserpic::PaintSavedMessages(
  349. QPainter &p,
  350. int x,
  351. int y,
  352. int outerWidth,
  353. int size,
  354. QBrush bg,
  355. const style::color &fg) {
  356. x = style::RightToLeft() ? (outerWidth - x - size) : x;
  357. PainterHighQualityEnabler hq(p);
  358. p.setBrush(std::move(bg));
  359. p.setPen(Qt::NoPen);
  360. p.drawEllipse(x, y, size, size);
  361. PaintSavedMessagesInner(p, x, y, size, fg);
  362. }
  363. QImage EmptyUserpic::GenerateSavedMessages(int size) {
  364. return Generate(size, [&](QPainter &p) {
  365. PaintSavedMessages(p, 0, 0, size, size);
  366. });
  367. }
  368. void EmptyUserpic::PaintRepliesMessages(
  369. QPainter &p,
  370. int x,
  371. int y,
  372. int outerWidth,
  373. int size) {
  374. auto bg = QLinearGradient(x, y, x, y + size);
  375. bg.setStops({
  376. { 0., st::historyPeerSavedMessagesBg->c },
  377. { 1., st::historyPeerSavedMessagesBg2->c }
  378. });
  379. const auto &fg = st::historyPeerUserpicFg;
  380. PaintRepliesMessages(p, x, y, outerWidth, size, QBrush(bg), fg);
  381. }
  382. void EmptyUserpic::PaintRepliesMessages(
  383. QPainter &p,
  384. int x,
  385. int y,
  386. int outerWidth,
  387. int size,
  388. QBrush bg,
  389. const style::color &fg) {
  390. x = style::RightToLeft() ? (outerWidth - x - size) : x;
  391. PainterHighQualityEnabler hq(p);
  392. p.setBrush(bg);
  393. p.setPen(Qt::NoPen);
  394. p.drawEllipse(x, y, size, size);
  395. PaintRepliesMessagesInner(p, x, y, size, fg);
  396. }
  397. QImage EmptyUserpic::GenerateRepliesMessages(int size) {
  398. return Generate(size, [&](QPainter &p) {
  399. PaintRepliesMessages(p, 0, 0, size, size);
  400. });
  401. }
  402. void EmptyUserpic::PaintHiddenAuthor(
  403. QPainter &p,
  404. int x,
  405. int y,
  406. int outerWidth,
  407. int size) {
  408. auto bg = QLinearGradient(x, y, x, y + size);
  409. bg.setStops({
  410. { 0., st::premiumButtonBg2->c },
  411. { 1., st::premiumButtonBg3->c },
  412. });
  413. const auto &fg = st::premiumButtonFg;
  414. PaintHiddenAuthor(p, x, y, outerWidth, size, QBrush(bg), fg);
  415. }
  416. void EmptyUserpic::PaintHiddenAuthor(
  417. QPainter &p,
  418. int x,
  419. int y,
  420. int outerWidth,
  421. int size,
  422. QBrush bg,
  423. const style::color &fg) {
  424. x = style::RightToLeft() ? (outerWidth - x - size) : x;
  425. PainterHighQualityEnabler hq(p);
  426. p.setBrush(bg);
  427. p.setPen(Qt::NoPen);
  428. p.drawEllipse(x, y, size, size);
  429. PaintHiddenAuthorInner(p, x, y, size, fg);
  430. }
  431. QImage EmptyUserpic::GenerateHiddenAuthor(int size) {
  432. return Generate(size, [&](QPainter &p) {
  433. PaintHiddenAuthor(p, 0, 0, size, size);
  434. });
  435. }
  436. void EmptyUserpic::PaintMyNotes(
  437. QPainter &p,
  438. int x,
  439. int y,
  440. int outerWidth,
  441. int size) {
  442. auto bg = QLinearGradient(x, y, x, y + size);
  443. bg.setStops({
  444. { 0., st::historyPeerSavedMessagesBg->c },
  445. { 1., st::historyPeerSavedMessagesBg2->c }
  446. });
  447. const auto &fg = st::historyPeerUserpicFg;
  448. PaintMyNotes(p, x, y, outerWidth, size, QBrush(bg), fg);
  449. }
  450. void EmptyUserpic::PaintMyNotes(
  451. QPainter &p,
  452. int x,
  453. int y,
  454. int outerWidth,
  455. int size,
  456. QBrush bg,
  457. const style::color &fg) {
  458. x = style::RightToLeft() ? (outerWidth - x - size) : x;
  459. PainterHighQualityEnabler hq(p);
  460. p.setBrush(bg);
  461. p.setPen(Qt::NoPen);
  462. p.drawEllipse(x, y, size, size);
  463. PaintMyNotesInner(p, x, y, size, fg);
  464. }
  465. QImage EmptyUserpic::GenerateMyNotes(int size) {
  466. return Generate(size, [&](QPainter &p) {
  467. PaintMyNotes(p, 0, 0, size, size);
  468. });
  469. }
  470. void EmptyUserpic::PaintCurrency(
  471. QPainter &p,
  472. int x,
  473. int y,
  474. int outerWidth,
  475. int size) {
  476. auto bg = QLinearGradient(x, y, x, y + size);
  477. bg.setStops({
  478. { 0., st::historyPeerSavedMessagesBg->c },
  479. { 1., st::historyPeerSavedMessagesBg2->c }
  480. });
  481. const auto &fg = st::historyPeerUserpicFg;
  482. PaintCurrency(p, x, y, outerWidth, size, QBrush(bg), fg);
  483. }
  484. void EmptyUserpic::PaintCurrency(
  485. QPainter &p,
  486. int x,
  487. int y,
  488. int outerWidth,
  489. int size,
  490. QBrush bg,
  491. const style::color &fg) {
  492. x = style::RightToLeft() ? (outerWidth - x - size) : x;
  493. PainterHighQualityEnabler hq(p);
  494. p.setBrush(bg);
  495. p.setPen(Qt::NoPen);
  496. p.drawEllipse(x, y, size, size);
  497. PaintCurrencyInner(p, x, y, size, fg);
  498. }
  499. QImage EmptyUserpic::GenerateCurrency(int size) {
  500. return Generate(size, [&](QPainter &p) {
  501. PaintCurrency(p, 0, 0, size, size);
  502. });
  503. }
  504. std::pair<uint64, uint64> EmptyUserpic::uniqueKey() const {
  505. const auto first = (uint64(0xFFFFFFFFU) << 32)
  506. | anim::getPremultiplied(_colors.color1->c);
  507. auto second = uint64(0);
  508. memcpy(
  509. &second,
  510. _string.constData(),
  511. std::min(sizeof(second), size_t(_string.size()) * sizeof(QChar)));
  512. return { first, second };
  513. }
  514. QPixmap EmptyUserpic::generate(int size) {
  515. auto result = QImage(
  516. QSize(size, size) * style::DevicePixelRatio(),
  517. QImage::Format_ARGB32_Premultiplied);
  518. result.setDevicePixelRatio(style::DevicePixelRatio());
  519. result.fill(Qt::transparent);
  520. {
  521. auto p = QPainter(&result);
  522. paintCircle(p, 0, 0, size, size);
  523. }
  524. return Ui::PixmapFromImage(std::move(result));
  525. }
  526. void EmptyUserpic::fillString(const QString &name) {
  527. if (IsExternal(name) || IsInaccessible(name)) {
  528. _string = name;
  529. return;
  530. }
  531. QList<QString> letters;
  532. QList<int> levels;
  533. auto level = 0;
  534. auto letterFound = false;
  535. auto ch = name.constData(), end = ch + name.size();
  536. while (ch != end) {
  537. auto emojiLength = 0;
  538. if (Ui::Emoji::Find(ch, end, &emojiLength)) {
  539. ch += emojiLength;
  540. } else if (ch->isHighSurrogate()) {
  541. ++ch;
  542. if (ch != end && ch->isLowSurrogate()) {
  543. ++ch;
  544. }
  545. } else if (!letterFound && ch->isLetterOrNumber()) {
  546. letterFound = true;
  547. if (ch + 1 != end && Ui::Text::IsDiacritic(*(ch + 1))) {
  548. letters.push_back(QString(ch, 2));
  549. levels.push_back(level);
  550. ++ch;
  551. } else {
  552. letters.push_back(QString(ch, 1));
  553. levels.push_back(level);
  554. }
  555. ++ch;
  556. } else {
  557. if (*ch == ' ') {
  558. level = 0;
  559. letterFound = false;
  560. } else if (letterFound && *ch == '-') {
  561. level = 1;
  562. letterFound = true;
  563. }
  564. ++ch;
  565. }
  566. }
  567. // We prefer the second letter to be after ' ', but it can also be after '-'.
  568. _string = QString();
  569. if (!letters.isEmpty()) {
  570. _string += letters.front();
  571. auto bestIndex = 0;
  572. auto bestLevel = 2;
  573. for (auto i = letters.size(); i != 1;) {
  574. if (levels[--i] < bestLevel) {
  575. bestIndex = i;
  576. bestLevel = levels[i];
  577. }
  578. }
  579. if (bestIndex > 0) {
  580. _string += letters[bestIndex];
  581. }
  582. }
  583. _string = _string.toUpper();
  584. }
  585. EmptyUserpic::~EmptyUserpic() = default;
  586. } // namespace Ui