window_theme_preview.cpp 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269
  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 "window/themes/window_theme_preview.h"
  8. #include "dialogs/dialogs_three_state_icon.h"
  9. #include "lang/lang_keys.h"
  10. #include "platform/platform_window_title.h"
  11. #include "ui/text/text_options.h"
  12. #include "ui/text/text_utilities.h"
  13. #include "ui/empty_userpic.h"
  14. #include "ui/emoji_config.h"
  15. #include "ui/painter.h"
  16. #include "ui/rect.h"
  17. #include "ui/chat/chat_theme.h"
  18. #include "ui/chat/chat_style.h"
  19. #include "ui/chat/message_bubble.h"
  20. #include "styles/style_widgets.h"
  21. #include "styles/style_window.h"
  22. #include "styles/style_media_view.h"
  23. #include "styles/style_chat.h"
  24. #include "styles/style_chat_helpers.h"
  25. #include "styles/style_dialogs.h"
  26. #include "styles/style_info.h"
  27. namespace Window {
  28. namespace Theme {
  29. namespace {
  30. [[nodiscard]] QString FillLetters(const QString &name) {
  31. QList<QString> letters;
  32. QList<int> levels;
  33. auto level = 0;
  34. auto letterFound = false;
  35. auto ch = name.constData(), end = ch + name.size();
  36. while (ch != end) {
  37. auto emojiLength = 0;
  38. if (Ui::Emoji::Find(ch, end, &emojiLength)) {
  39. ch += emojiLength;
  40. } else if (ch->isHighSurrogate()) {
  41. ++ch;
  42. if (ch != end && ch->isLowSurrogate()) {
  43. ++ch;
  44. }
  45. } else if (!letterFound && ch->isLetterOrNumber()) {
  46. letterFound = true;
  47. if (ch + 1 != end && Ui::Text::IsDiacritic(*(ch + 1))) {
  48. letters.push_back(QString(ch, 2));
  49. levels.push_back(level);
  50. ++ch;
  51. } else {
  52. letters.push_back(QString(ch, 1));
  53. levels.push_back(level);
  54. }
  55. ++ch;
  56. } else {
  57. if (*ch == ' ') {
  58. level = 0;
  59. letterFound = false;
  60. } else if (letterFound && *ch == '-') {
  61. level = 1;
  62. letterFound = true;
  63. }
  64. ++ch;
  65. }
  66. }
  67. // We prefer the second letter to be after ' ', but it can also be after '-'.
  68. auto result = QString();
  69. if (!letters.isEmpty()) {
  70. result += letters.front();
  71. auto bestIndex = 0;
  72. auto bestLevel = 2;
  73. for (auto i = letters.size(); i != 1;) {
  74. if (levels[--i] < bestLevel) {
  75. bestIndex = i;
  76. bestLevel = levels[i];
  77. }
  78. }
  79. if (bestIndex > 0) {
  80. result += letters[bestIndex];
  81. }
  82. }
  83. return result.toUpper();
  84. }
  85. class Generator {
  86. public:
  87. Generator(
  88. const Instance &theme,
  89. CurrentData &&current,
  90. PreviewType type);
  91. [[nodiscard]] QImage generate();
  92. private:
  93. enum class Status {
  94. None,
  95. Sent,
  96. Received
  97. };
  98. struct Row {
  99. Ui::Text::String name;
  100. QString letters;
  101. enum class Type {
  102. User,
  103. Group,
  104. Channel
  105. };
  106. Type type = Type::User;
  107. int peerIndex = 0;
  108. int unreadCounter = 0;
  109. bool muted = false;
  110. bool pinned = false;
  111. QString date;
  112. Ui::Text::String text;
  113. Status status = Status::None;
  114. bool selected = false;
  115. bool active = false;
  116. };
  117. struct Bubble {
  118. int width = 0;
  119. int height = 0;
  120. bool outbg = false;
  121. Status status = Status::None;
  122. QString date;
  123. bool attachToTop = false;
  124. bool attachToBottom = false;
  125. bool tail = true;
  126. Ui::Text::String text = { st::msgMinWidth };
  127. QVector<int> waveform;
  128. int waveactive = 0;
  129. QString wavestatus;
  130. QImage photo;
  131. int photoWidth = 0;
  132. int photoHeight = 0;
  133. Ui::Text::String replyName = { st::msgMinWidth };
  134. Ui::Text::String replyText = { st::msgMinWidth };
  135. };
  136. [[nodiscard]] bool extended() const;
  137. void prepare();
  138. void addRow(
  139. QString name,
  140. int peerIndex,
  141. QString date,
  142. const TextWithEntities &text);
  143. void addBubble(Bubble bubble, int width, int height, QString date, Status status);
  144. void addAudioBubble(QVector<int> waveform, int waveactive, QString wavestatus, QString date, Status status);
  145. void addTextBubble(QString text, QString date, Status status);
  146. void addDateBubble(QString date);
  147. void addPhotoBubble(QString image, QString caption, QString date, Status status);
  148. QSize computeSkipBlock(Status status, QString date);
  149. int computeInfoWidth(Status status, QString date);
  150. void generateData();
  151. void paintHistoryList();
  152. void paintHistoryBackground();
  153. void paintTopBar();
  154. void paintComposeArea();
  155. void paintDialogs();
  156. void paintDialogsList();
  157. void paintHistoryShadows();
  158. void paintRow(const Row &row);
  159. void paintBubble(const Bubble &bubble);
  160. void paintService(QString text);
  161. void paintUserpic(int x, int y, Row::Type type, int index, QString letters);
  162. void setTextPalette(const style::TextPalette &st);
  163. void restoreTextPalette();
  164. const Instance &_theme;
  165. const style::palette &_palette;
  166. const CurrentData _current;
  167. const PreviewType _type;
  168. Ui::ChatStyle _st;
  169. Painter *_p = nullptr;
  170. QRect _rect;
  171. QRect _inner;
  172. QRect _body;
  173. QRect _dialogs;
  174. QRect _dialogsList;
  175. QRect _topBar;
  176. QRect _composeArea;
  177. QRect _history;
  178. int _rowsTop = 0;
  179. std::vector<Row> _rows;
  180. Ui::Text::String _topBarName;
  181. QString _topBarStatus;
  182. bool _topBarStatusActive = false;
  183. int _historyBottom = 0;
  184. std::vector<Bubble> _bubbles;
  185. style::TextPalette _textPalette;
  186. };
  187. bool Generator::extended() const {
  188. return (_type == PreviewType::Extended);
  189. }
  190. void Generator::prepare() {
  191. const auto size = extended()
  192. ? QRect(
  193. QPoint(),
  194. st::themePreviewSize).marginsAdded(st::themePreviewMargin).size()
  195. : st::themePreviewSize;
  196. _rect = QRect(QPoint(), size);
  197. _inner = extended() ? _rect.marginsRemoved(st::themePreviewMargin) : _rect;
  198. _body = extended() ? _inner.marginsRemoved(QMargins(0, Platform::PreviewTitleHeight(), 0, 0)) : _inner;
  199. _dialogs = QRect(_body.x(), _body.y(), st::themePreviewDialogsWidth, _body.height());
  200. _dialogsList = _dialogs.marginsRemoved(QMargins(0, st::dialogsFilterPadding.y() + st::dialogsMenuToggle.height + st::dialogsFilterPadding.y(), 0, st::defaultDialogRow.padding.bottom()));
  201. _topBar = QRect(_dialogs.x() + _dialogs.width(), _dialogs.y(), _body.width() - _dialogs.width(), st::topBarHeight);
  202. _composeArea = QRect(_topBar.x(), _body.y() + _body.height() - st::historySendSize.height(), _topBar.width(), st::historySendSize.height());
  203. _history = QRect(_topBar.x(), _topBar.y() + _topBar.height(), _topBar.width(), _body.height() - _topBar.height() - _composeArea.height());
  204. generateData();
  205. }
  206. void Generator::addRow(
  207. QString name,
  208. int peerIndex,
  209. QString date,
  210. const TextWithEntities &text) {
  211. Row row;
  212. row.name.setText(st::msgNameStyle, name, Ui::NameTextOptions());
  213. row.letters = FillLetters(name);
  214. row.peerIndex = peerIndex;
  215. row.date = date;
  216. row.text.setMarkedText(
  217. st::dialogsTextStyle,
  218. text,
  219. Ui::DialogTextOptions());
  220. _rows.push_back(std::move(row));
  221. }
  222. void Generator::addBubble(Bubble bubble, int width, int height, QString date, Status status) {
  223. bubble.width = width;
  224. bubble.height = height;
  225. bubble.date = date;
  226. bubble.status = status;
  227. _bubbles.push_back(std::move(bubble));
  228. }
  229. void Generator::addAudioBubble(QVector<int> waveform, int waveactive, QString wavestatus, QString date, Status status) {
  230. Bubble bubble;
  231. bubble.waveform = waveform;
  232. bubble.waveactive = waveactive;
  233. bubble.wavestatus = wavestatus;
  234. auto skipBlock = computeSkipBlock(status, date);
  235. auto width = st::msgFileMinWidth;
  236. const auto &st = st::msgFileLayout;
  237. auto tleft = st.padding.left() + st.thumbSize + st.thumbSkip;
  238. accumulate_max(width, tleft + st::normalFont->width(wavestatus) + skipBlock.width() + st::msgPadding.right());
  239. accumulate_min(width, st::msgMaxWidth);
  240. auto height = st.padding.top() + st.thumbSize + st.padding.bottom();
  241. addBubble(std::move(bubble), width, height, date, status);
  242. }
  243. QSize Generator::computeSkipBlock(Status status, QString date) {
  244. auto infoWidth = computeInfoWidth(status, date);
  245. auto width = st::msgDateSpace + infoWidth - st::msgDateDelta.x();
  246. auto height = st::msgDateFont->height - st::msgDateDelta.y();
  247. return QSize(width, height);
  248. }
  249. int Generator::computeInfoWidth(Status status, QString date) {
  250. auto result = st::msgDateFont->width(date);
  251. if (status != Status::None) {
  252. result += st::historySendStateSpace;
  253. }
  254. return result;
  255. }
  256. void Generator::addTextBubble(QString text, QString date, Status status) {
  257. Bubble bubble;
  258. auto skipBlock = computeSkipBlock(status, date);
  259. auto marked = TextWithEntities{ std::move(text) };
  260. bubble.text.setMarkedText(
  261. st::messageTextStyle,
  262. std::move(marked),
  263. Ui::ItemTextDefaultOptions());
  264. bubble.text.updateSkipBlock(skipBlock.width(), skipBlock.height());
  265. auto width = _history.width() - st::msgMargin.left() - st::msgMargin.right();
  266. accumulate_min(width, st::msgPadding.left() + bubble.text.maxWidth() + st::msgPadding.right());
  267. accumulate_min(width, st::msgMaxWidth);
  268. auto textWidth = qMax(width - st::msgPadding.left() - st::msgPadding.right(), 1);
  269. auto textHeight = bubble.text.countHeight(textWidth);
  270. auto height = st::msgPadding.top() + textHeight + st::msgPadding.bottom();
  271. addBubble(std::move(bubble), width, height, date, status);
  272. }
  273. void Generator::addDateBubble(QString date) {
  274. Bubble bubble;
  275. addBubble(std::move(bubble), 0, 0, date, Status::None);
  276. }
  277. void Generator::addPhotoBubble(QString image, QString caption, QString date, Status status) {
  278. Bubble bubble;
  279. bubble.photo.load(image);
  280. bubble.photoWidth = style::ConvertScale(bubble.photo.width() / 2);
  281. bubble.photoHeight = style::ConvertScale(bubble.photo.height() / 2);
  282. auto skipBlock = computeSkipBlock(status, date);
  283. auto marked = TextWithEntities{ std::move(caption) };
  284. bubble.text.setMarkedText(
  285. st::messageTextStyle,
  286. std::move(marked),
  287. Ui::ItemTextDefaultOptions());
  288. bubble.text.updateSkipBlock(skipBlock.width(), skipBlock.height());
  289. auto width = _history.width() - st::msgMargin.left() - st::msgMargin.right();
  290. accumulate_min(width, bubble.photoWidth);
  291. accumulate_min(width, st::msgMaxWidth);
  292. auto textWidth = qMax(width - st::msgPadding.left() - st::msgPadding.right(), 1);
  293. auto textHeight = bubble.text.countHeight(textWidth);
  294. auto height = st::mediaCaptionSkip + textHeight + st::msgPadding.bottom();
  295. addBubble(std::move(bubble), width, height, date, status);
  296. }
  297. void Generator::generateData() {
  298. _rows.reserve(9);
  299. addRow(
  300. "Eva Summer",
  301. 0,
  302. "11:00",
  303. { .text = "We are too smart for this world. "
  304. + QString::fromUtf8("\xf0\x9f\xa4\xa3\xf0\x9f\x98\x82") });
  305. _rows.back().active = true;
  306. _rows.back().pinned = true;
  307. addRow("Alexandra Smith", 7, "10:00", { .text = "This is amazing!" });
  308. _rows.back().unreadCounter = 2;
  309. addRow(
  310. "Mike Apple",
  311. 2,
  312. "9:00",
  313. Ui::Text::Colorized(QChar(55357)
  314. + QString()
  315. + QChar(56836)
  316. + " Sticker"));
  317. _rows.back().unreadCounter = 2;
  318. _rows.back().muted = true;
  319. addRow("Evening Club", 1, "8:00", Ui::Text::Colorized("Eva: Photo"));
  320. _rows.back().type = Row::Type::Group;
  321. addRow(
  322. "Old Pirates",
  323. 6,
  324. "7:00",
  325. Ui::Text::Colorized("Max:").append(" Yo-ho-ho!"));
  326. _rows.back().type = Row::Type::Group;
  327. addRow("Max Bright", 3, "6:00", { .text = "How about some coffee?" });
  328. _rows.back().status = Status::Received;
  329. addRow("Natalie Parker", 4, "5:00", { .text = "OK, great)" });
  330. _rows.back().status = Status::Received;
  331. addRow("Davy Jones", 5, "4:00", Ui::Text::Colorized("Keynote.pdf"));
  332. _topBarName.setText(st::msgNameStyle, "Eva Summer", Ui::NameTextOptions());
  333. _topBarStatus = "online";
  334. _topBarStatusActive = true;
  335. addPhotoBubble(":/gui/art/themeimage.jpg", "To reach a port, we must sail. " + QString::fromUtf8("\xf0\x9f\xa5\xb8"), "7:00", Status::None);
  336. int wavedata[] = { 0, 0, 0, 0, 27, 31, 4, 1, 0, 0, 23, 30, 18, 9, 7, 19, 4, 2, 2, 2, 0, 0, 15, 15, 15, 15, 3, 15, 19, 3, 2, 0, 0, 0, 0, 0, 3, 12, 16, 6, 4, 6, 14, 12, 2, 12, 12, 11, 3, 0, 7, 5, 7, 4, 7, 5, 2, 4, 0, 9, 5, 7, 6, 2, 2, 0, 0 };
  337. auto waveform = QVector<int>(base::array_size(wavedata));
  338. memcpy(waveform.data(), wavedata, sizeof(wavedata));
  339. addAudioBubble(waveform, 33, "0:07", "8:00", Status::None);
  340. _bubbles.back().outbg = true;
  341. _bubbles.back().status = Status::Received;
  342. addDateBubble("December 26");
  343. addTextBubble("Twenty years from now you will be more disappointed by the things that you didn't do than by the ones you did do. " + QString::fromUtf8("\xf0\x9f\xa7\x90"), "10:00", Status::Received);
  344. _bubbles.back().tail = false;
  345. _bubbles.back().outbg = true;
  346. _bubbles.back().attachToBottom = true;
  347. addTextBubble("Mark Twain said that " + QString::fromUtf8("\xe2\x98\x9d\xef\xb8\x8f"), "10:00", Status::Received);
  348. _bubbles.back().outbg = true;
  349. _bubbles.back().attachToTop = true;
  350. _bubbles.back().tail = true;
  351. addTextBubble("We are too smart for this world. " + QString::fromUtf8("\xf0\x9f\xa4\xa3\xf0\x9f\x98\x82"), "11:00", Status::None);
  352. _bubbles.back().replyName.setText(st::msgNameStyle, "Alex Cassio", Ui::NameTextOptions());
  353. _bubbles.back().replyText.setText(st::messageTextStyle, "Mark Twain said that " + QString::fromUtf8("\xe2\x98\x9d\xef\xb8\x8f"), Ui::DialogTextOptions());
  354. }
  355. Generator::Generator(
  356. const Instance &theme,
  357. CurrentData &&current,
  358. PreviewType type)
  359. : _theme(theme)
  360. , _palette(_theme.palette)
  361. , _current(std::move(current))
  362. , _type(type)
  363. , _st(&_palette) {
  364. }
  365. QImage Generator::generate() {
  366. prepare();
  367. auto result = QImage(
  368. _rect.size() * style::DevicePixelRatio(),
  369. QImage::Format_ARGB32_Premultiplied);
  370. result.setDevicePixelRatio(style::DevicePixelRatio());
  371. result.fill(st::themePreviewBg->c);
  372. {
  373. Painter p(&result);
  374. PainterHighQualityEnabler hq(p);
  375. _p = &p;
  376. _p->fillRect(_body, QColor(0, 0, 0));
  377. _p->fillRect(_body, st::windowBg[_palette]);
  378. paintHistoryList();
  379. paintTopBar();
  380. paintComposeArea();
  381. paintDialogs();
  382. paintHistoryShadows();
  383. }
  384. if (extended()) {
  385. Platform::PreviewWindowFramePaint(result, _palette, _body, _rect.width());
  386. }
  387. return result;
  388. }
  389. void Generator::paintHistoryList() {
  390. paintHistoryBackground();
  391. _historyBottom = _history.y() + _history.height();
  392. _historyBottom -= st::historyPaddingBottom;
  393. _p->setClipping(true);
  394. for (auto i = _bubbles.size(); i != 0;) {
  395. auto &bubble = _bubbles[--i];
  396. if (bubble.width > 0) {
  397. paintBubble(bubble);
  398. } else {
  399. paintService(bubble.date);
  400. }
  401. }
  402. _p->setClipping(false);
  403. }
  404. void Generator::paintHistoryBackground() {
  405. auto fromy = (-st::topBarHeight);
  406. auto background = _theme.background;
  407. auto tiled = _theme.tiled;
  408. if (background.isNull()) {
  409. const auto fakePaper = Data::WallPaper(_current.backgroundId);
  410. if (Data::IsThemeWallPaper(fakePaper)) {
  411. background = Ui::ReadBackgroundImage(
  412. u":/gui/art/background.tgv"_q,
  413. QByteArray(),
  414. true);
  415. const auto paper = Data::DefaultWallPaper();
  416. background = Ui::PreparePatternImage(
  417. std::move(background),
  418. paper.backgroundColors(),
  419. paper.gradientRotation(),
  420. paper.patternOpacity());
  421. tiled = false;
  422. } else {
  423. background = std::move(_current.backgroundImage);
  424. tiled = _current.backgroundTiled;
  425. }
  426. }
  427. background = std::move(background).convertToFormat(
  428. QImage::Format_ARGB32_Premultiplied);
  429. background.setDevicePixelRatio(style::DevicePixelRatio());
  430. _p->setClipRect(_history);
  431. if (tiled) {
  432. auto width = background.width();
  433. auto height = background.height();
  434. auto repeatTimesX = qCeil(_history.width()
  435. * style::DevicePixelRatio()
  436. / float64(width));
  437. auto repeatTimesY = qCeil((_history.height() - fromy)
  438. * style::DevicePixelRatio()
  439. / float64(height));
  440. auto imageForTiled = QImage(
  441. width * repeatTimesX,
  442. height * repeatTimesY,
  443. QImage::Format_ARGB32_Premultiplied);
  444. imageForTiled.setDevicePixelRatio(background.devicePixelRatio());
  445. auto imageForTiledBytes = imageForTiled.bits();
  446. auto bytesInLine = width * sizeof(uint32);
  447. for (auto timesY = 0; timesY != repeatTimesY; ++timesY) {
  448. auto imageBytes = background.constBits();
  449. for (auto y = 0; y != height; ++y) {
  450. for (auto timesX = 0; timesX != repeatTimesX; ++timesX) {
  451. memcpy(imageForTiledBytes, imageBytes, bytesInLine);
  452. imageForTiledBytes += bytesInLine;
  453. }
  454. imageBytes += background.bytesPerLine();
  455. imageForTiledBytes += imageForTiled.bytesPerLine()
  456. - (repeatTimesX * bytesInLine);
  457. }
  458. }
  459. _p->drawImage(_history.x(), _history.y() + fromy, imageForTiled);
  460. } else {
  461. PainterHighQualityEnabler hq(*_p);
  462. auto fill = QSize(_topBar.width(), _body.height());
  463. const auto rects = Ui::ComputeChatBackgroundRects(
  464. fill,
  465. background.size());
  466. auto to = rects.to;
  467. to.moveTop(to.top() + fromy);
  468. to.moveTopLeft(to.topLeft() + _history.topLeft());
  469. _p->drawImage(to, background, rects.from);
  470. }
  471. _p->setClipping(false);
  472. }
  473. void Generator::paintTopBar() {
  474. _p->fillRect(_topBar, st::topBarBg[_palette]);
  475. auto right = st::topBarMenuToggle.width;
  476. st::topBarMenuToggle.icon[_palette].paint(*_p, _topBar.x() + _topBar.width() - right + st::topBarMenuToggle.iconPosition.x(), _topBar.y() + st::topBarMenuToggle.iconPosition.y(), _rect.width());
  477. right += st::topBarSkip + st::topBarCall.width;
  478. st::topBarCall.icon[_palette].paint(*_p, _topBar.x() + _topBar.width() - right + st::topBarCall.iconPosition.x(), _topBar.y() + st::topBarCall.iconPosition.y(), _rect.width());
  479. right += st::topBarSearch.width;
  480. st::topBarSearch.icon[_palette].paint(*_p, _topBar.x() + _topBar.width() - right + st::topBarSearch.iconPosition.x(), _topBar.y() + st::topBarSearch.iconPosition.y(), _rect.width());
  481. auto decreaseWidth = st::topBarCall.width + st::topBarCallSkip + st::topBarSearch.width + st::topBarMenuToggle.width;
  482. auto nameleft = _topBar.x() + st::topBarArrowPadding.right();
  483. auto nametop = _topBar.y() + st::topBarArrowPadding.top();
  484. auto statustop = _topBar.y() + st::topBarHeight - st::topBarArrowPadding.bottom() - st::dialogsTextFont->height;
  485. auto namewidth = _topBar.x() + _topBar.width() - decreaseWidth - nameleft - st::topBarArrowPadding.right();
  486. _p->setFont(st::dialogsTextFont);
  487. _p->setPen(_topBarStatusActive ? st::historyStatusFgActive[_palette] : st::historyStatusFg[_palette]);
  488. _p->drawText(nameleft, statustop + st::dialogsTextFont->ascent, _topBarStatus);
  489. _p->setPen(st::dialogsNameFg[_palette]);
  490. _topBarName.drawElided(*_p, nameleft, nametop, namewidth);
  491. }
  492. void Generator::paintComposeArea() {
  493. _p->fillRect(_composeArea, st::historyReplyBg[_palette]);
  494. auto controlsTop = _composeArea.y() + _composeArea.height() - st::historySendSize.height();
  495. const auto attachIconLeft = (st::historyAttach.iconPosition.x() < 0)
  496. ? ((st::historyAttach.width - st::historyAttach.icon.width()) / 2)
  497. : st::historyAttach.iconPosition.x();
  498. const auto attachIconTop = (st::historyAttach.iconPosition.y() < 0)
  499. ? ((st::historyAttach.height - st::historyAttach.icon.height()) / 2)
  500. : st::historyAttach.iconPosition.y();
  501. st::historyAttach.icon[_palette].paint(*_p, _composeArea.x() + attachIconLeft, controlsTop + attachIconTop, _rect.width());
  502. auto right = st::historySendRight + st::historySendSize.width();
  503. st::historyRecordVoice[_palette].paintInCenter(*_p, QRect(_composeArea.x() + _composeArea.width() - right, controlsTop, st::historySendSize.width(), st::historySendSize.height()));
  504. const auto &emojiButton = st::historyAttachEmoji.inner;
  505. const auto emojiIconLeft = (emojiButton.iconPosition.x() < 0)
  506. ? ((emojiButton.width - emojiButton.icon.width()) / 2)
  507. : emojiButton.iconPosition.x();
  508. const auto emojiIconTop = (emojiButton.iconPosition.y() < 0)
  509. ? ((emojiButton.height - emojiButton.icon.height()) / 2)
  510. : emojiButton.iconPosition.y();
  511. const auto &emojiIcon = emojiButton.icon[_palette];
  512. right += emojiButton.width;
  513. auto attachEmojiLeft = _composeArea.x() + _composeArea.width() - right;
  514. _p->fillRect(attachEmojiLeft, controlsTop, emojiButton.width, emojiButton.height, st::historyComposeAreaBg[_palette]);
  515. emojiIcon.paint(*_p, attachEmojiLeft + emojiIconLeft, controlsTop + emojiIconTop, _rect.width());
  516. auto pen = st::historyEmojiCircleFg[_palette]->p;
  517. pen.setWidthF(style::ConvertScaleExact(st::historyEmojiCircleLine));
  518. pen.setCapStyle(Qt::RoundCap);
  519. _p->setPen(pen);
  520. _p->setBrush(Qt::NoBrush);
  521. PainterHighQualityEnabler hq(*_p);
  522. const auto skipx = emojiIcon.width() / 4;
  523. const auto skipy = emojiIcon.height() / 4;
  524. const auto inner = QRect(
  525. attachEmojiLeft + emojiIconLeft + skipx,
  526. controlsTop + emojiIconTop + skipy,
  527. emojiIcon.width() - 2 * skipx,
  528. emojiIcon.height() - 2 * skipy);
  529. _p->drawEllipse(inner);
  530. auto fieldLeft = _composeArea.x() + st::historyAttach.width;
  531. auto fieldTop = _composeArea.y() + _composeArea.height() - st::historyAttach.height + st::historySendPadding;
  532. auto fieldWidth = _composeArea.width() - st::historyAttach.width - st::historySendSize.width() - st::historySendRight - emojiButton.width;
  533. auto fieldHeight = st::historySendSize.height() - 2 * st::historySendPadding;
  534. auto field = QRect(fieldLeft, fieldTop, fieldWidth, fieldHeight);
  535. _p->fillRect(field, st::historyComposeField.textBg[_palette]);
  536. _p->setClipRect(field);
  537. _p->save();
  538. _p->setFont(st::historyComposeField.style.font);
  539. _p->setPen(st::historyComposeField.placeholderFg[_palette]);
  540. auto placeholderRect = QRect(
  541. field.x() + st::historyComposeField.textMargins.left() + st::historyComposeField.placeholderMargins.left(),
  542. field.y() + st::historyComposeField.textMargins.top() + st::historyComposeField.placeholderMargins.top(),
  543. field.width() - st::historyComposeField.textMargins.left() - st::historyComposeField.textMargins.right(),
  544. field.height() - st::historyComposeField.textMargins.top() - st::historyComposeField.textMargins.bottom());
  545. _p->drawText(placeholderRect, tr::lng_message_ph(tr::now), QTextOption(st::historyComposeField.placeholderAlign));
  546. _p->restore();
  547. _p->setClipping(false);
  548. }
  549. void Generator::paintDialogs() {
  550. _p->fillRect(_dialogs, st::dialogsBg[_palette]);
  551. const auto iconLeft = (st::dialogsMenuToggle.iconPosition.x() < 0)
  552. ? (st::dialogsMenuToggle.width - st::dialogsMenuToggle.icon.width()) / 2
  553. : st::dialogsMenuToggle.iconPosition.x();
  554. const auto iconTop = (st::dialogsMenuToggle.iconPosition.y() < 0)
  555. ? (st::dialogsMenuToggle.height - st::dialogsMenuToggle.icon.height()) / 2
  556. : st::dialogsMenuToggle.iconPosition.y();
  557. st::dialogsMenuToggle.icon[_palette].paint(*_p, _dialogs.x() + st::dialogsFilterPadding.x() + iconLeft, _dialogs.y() + st::dialogsFilterPadding.y() + iconTop, _rect.width());
  558. auto filterLeft = _dialogs.x() + st::dialogsFilterPadding.x() + st::dialogsMenuToggle.width + st::dialogsFilterPadding.x();
  559. auto filterRight = st::dialogsFilterSkip + st::dialogsFilterPadding.x();
  560. auto filterWidth = _dialogs.x() + _dialogs.width() - filterLeft - filterRight;
  561. auto filterAreaHeight = st::topBarHeight;
  562. auto filterTop = _dialogs.y() + (filterAreaHeight - st::dialogsFilter.heightMin) / 2;
  563. auto filter = QRect(filterLeft, filterTop, filterWidth, st::dialogsFilter.heightMin);
  564. auto pen = st::dialogsFilter.borderFg[_palette]->p;
  565. pen.setWidth(st::dialogsFilter.border);
  566. _p->setPen(pen);
  567. _p->setBrush(st::dialogsFilter.textBg[_palette]);
  568. {
  569. PainterHighQualityEnabler hq(*_p);
  570. const auto radius = st::dialogsFilter.borderRadius
  571. - (st::dialogsFilter.border / 2.);
  572. _p->drawRoundedRect(
  573. QRectF(filter).marginsRemoved(
  574. QMarginsF(
  575. st::dialogsFilter.border / 2.,
  576. st::dialogsFilter.border / 2.,
  577. st::dialogsFilter.border / 2.,
  578. st::dialogsFilter.border / 2.)),
  579. radius,
  580. radius);
  581. }
  582. _p->save();
  583. _p->setClipRect(filter);
  584. auto phRect = QRect(filter.x() + st::dialogsFilter.textMargins.left() + st::dialogsFilter.placeholderMargins.left(), filter.y() + st::dialogsFilter.textMargins.top() + st::dialogsFilter.placeholderMargins.top(), filter.width() - st::dialogsFilter.textMargins.left() - st::dialogsFilter.textMargins.right(), filter.height() - st::dialogsFilter.textMargins.top() - st::dialogsFilter.textMargins.bottom());
  585. _p->setFont(st::dialogsFilter.style.font);
  586. _p->setPen(st::dialogsFilter.placeholderFg[_palette]);
  587. _p->drawText(phRect, tr::lng_dlg_filter(tr::now), QTextOption(st::dialogsFilter.placeholderAlign));
  588. _p->restore();
  589. _p->setClipping(false);
  590. paintDialogsList();
  591. }
  592. void Generator::paintDialogsList() {
  593. _p->setClipRect(_dialogsList);
  594. _rowsTop = _dialogsList.y();
  595. for (auto &row : _rows) {
  596. paintRow(row);
  597. _rowsTop += st::dialogsRowHeight;
  598. }
  599. _p->setClipping(false);
  600. }
  601. void Generator::paintRow(const Row &row) {
  602. auto x = _dialogsList.x();
  603. auto y = _rowsTop;
  604. auto fullWidth = _dialogsList.width();
  605. auto fullRect = QRect(x, y, fullWidth, st::dialogsRowHeight);
  606. if (row.active || row.selected) {
  607. _p->fillRect(fullRect, row.active ? st::dialogsBgActive[_palette] : st::dialogsBgOver[_palette]);
  608. }
  609. const auto &st = st::defaultDialogRow;
  610. paintUserpic(
  611. x + st.padding.left(),
  612. y + st.padding.top(),
  613. row.type,
  614. row.peerIndex,
  615. row.letters);
  616. auto nameleft = x + st.nameLeft;
  617. auto namewidth = x + fullWidth - nameleft - st.padding.right();
  618. auto rectForName = QRect(nameleft, y + st.nameTop, namewidth, st::msgNameFont->height);
  619. auto chatTypeIcon = ([&row]() -> const style::icon * {
  620. if (row.type == Row::Type::Group) {
  621. return &Dialogs::ThreeStateIcon(
  622. st::dialogsChatIcon,
  623. row.active,
  624. row.selected);
  625. } else if (row.type == Row::Type::Channel) {
  626. return &Dialogs::ThreeStateIcon(
  627. st::dialogsChannelIcon,
  628. row.active,
  629. row.selected);
  630. }
  631. return nullptr;
  632. })();
  633. if (chatTypeIcon) {
  634. (*chatTypeIcon)[_palette].paint(*_p, rectForName.topLeft(), fullWidth);
  635. rectForName.setLeft(rectForName.left()
  636. + chatTypeIcon->width()
  637. + st::dialogsChatTypeSkip);
  638. }
  639. auto texttop = y + st.textTop;
  640. auto dateWidth = st::dialogsDateFont->width(row.date);
  641. rectForName.setWidth(rectForName.width() - dateWidth - st::dialogsDateSkip);
  642. _p->setFont(st::dialogsDateFont);
  643. _p->setPen(row.active ? st::dialogsDateFgActive[_palette] : (row.selected ? st::dialogsDateFgOver[_palette] : st::dialogsDateFg[_palette]));
  644. _p->drawText(rectForName.left() + rectForName.width() + st::dialogsDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, row.date);
  645. auto availableWidth = namewidth;
  646. if (row.unreadCounter) {
  647. auto counter = QString::number(row.unreadCounter);
  648. auto unreadRight = x + fullWidth - st.padding.right();
  649. auto unreadTop = texttop + st::dialogsTextFont->ascent - st::dialogsUnreadFont->ascent - (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2;
  650. auto unreadWidth = st::dialogsUnreadFont->width(counter);
  651. auto unreadRectWidth = unreadWidth + 2 * st::dialogsUnreadPadding;
  652. auto unreadRectHeight = st::dialogsUnreadHeight;
  653. accumulate_max(unreadRectWidth, unreadRectHeight);
  654. auto unreadRectLeft = unreadRight - unreadRectWidth;
  655. auto unreadRectTop = unreadTop;
  656. availableWidth -= unreadRectWidth + st::dialogsUnreadPadding;
  657. style::color bg[] = {
  658. st::dialogsUnreadBg,
  659. st::dialogsUnreadBgOver,
  660. st::dialogsUnreadBgActive,
  661. st::dialogsUnreadBgMuted,
  662. st::dialogsUnreadBgMutedOver,
  663. st::dialogsUnreadBgMutedActive
  664. };
  665. auto index = (row.active ? 2 : row.selected ? 1 : 0) + (row.muted ? 3 : 0);
  666. _p->setPen(Qt::NoPen);
  667. _p->setBrush(bg[index][_palette]);
  668. _p->drawRoundedRect(QRectF(unreadRectLeft, unreadRectTop, unreadRectWidth, unreadRectHeight), unreadRectHeight / 2., unreadRectHeight / 2.);
  669. auto textTop = (unreadRectHeight - st::dialogsUnreadFont->height) / 2;
  670. _p->setFont(st::dialogsUnreadFont);
  671. _p->setPen(row.active ? st::dialogsUnreadFgActive[_palette] : (row.selected ? st::dialogsUnreadFgOver[_palette] : st::dialogsUnreadFg[_palette]));
  672. _p->drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + textTop + st::dialogsUnreadFont->ascent, counter);
  673. } else if (row.pinned) {
  674. auto icon = Dialogs::ThreeStateIcon(
  675. st::dialogsPinnedIcon,
  676. row.active,
  677. row.selected)[_palette];
  678. icon.paint(*_p, x + fullWidth - st.padding.right() - icon.width(), texttop, fullWidth);
  679. availableWidth -= icon.width() + st::dialogsUnreadPadding;
  680. }
  681. auto textRect = QRect(nameleft, texttop, availableWidth, st::dialogsTextFont->height);
  682. setTextPalette(row.active ? st::dialogsTextPaletteActive : (row.selected ? st::dialogsTextPaletteOver : st::dialogsTextPalette));
  683. _p->setFont(st::dialogsTextFont);
  684. _p->setPen(row.active ? st::dialogsTextFgActive[_palette] : (row.selected ? st::dialogsTextFgOver[_palette] : st::dialogsTextFg[_palette]));
  685. row.text.drawElided(*_p, textRect.left(), textRect.top(), textRect.width(), textRect.height() / st::dialogsTextFont->height);
  686. restoreTextPalette();
  687. auto sendStateIcon = ([&row]() -> const style::icon* {
  688. if (row.status == Status::Sent) {
  689. return &Dialogs::ThreeStateIcon(
  690. st::dialogsSentIcon,
  691. row.active,
  692. row.selected);
  693. } else if (row.status == Status::Received) {
  694. return &Dialogs::ThreeStateIcon(
  695. st::dialogsReceivedIcon,
  696. row.active,
  697. row.selected);
  698. }
  699. return nullptr;
  700. })();
  701. if (sendStateIcon) {
  702. rectForName.setWidth(rectForName.width() - st::dialogsSendStateSkip);
  703. (*sendStateIcon)[_palette].paint(*_p, rectForName.topLeft() + QPoint(rectForName.width(), 0), fullWidth);
  704. }
  705. _p->setPen(row.active ? st::dialogsNameFgActive[_palette] : (row.selected ? st::dialogsNameFgOver[_palette] : st::dialogsNameFg[_palette]));
  706. row.name.drawElided(*_p, rectForName.left(), rectForName.top(), rectForName.width());
  707. }
  708. void Generator::paintBubble(const Bubble &bubble) {
  709. auto height = bubble.height;
  710. if (!bubble.replyName.isEmpty()) {
  711. height += st::historyReplyTop
  712. + st::historyReplyPadding.top()
  713. + st::msgServiceNameFont->height
  714. + st::normalFont->height
  715. + st::historyReplyPadding.bottom()
  716. + st::historyReplyBottom;
  717. }
  718. auto isPhoto = !bubble.photo.isNull();
  719. auto x = _history.x();
  720. auto y = _historyBottom - st::msgMargin.bottom() - height;
  721. auto bubbleTop = y;
  722. auto bubbleHeight = height;
  723. if (isPhoto) {
  724. bubbleTop -= Ui::BubbleRadiusLarge() + 1;
  725. bubbleHeight += Ui::BubbleRadiusLarge() + 1;
  726. }
  727. auto left = bubble.outbg ? st::msgMargin.right() : st::msgMargin.left();
  728. if (bubble.outbg) {
  729. left += _history.width() - st::msgMargin.left() - st::msgMargin.right() - bubble.width;
  730. }
  731. x += left;
  732. using Corner = Ui::BubbleCornerRounding;
  733. auto rounding = Ui::BubbleRounding{
  734. Corner::Large,
  735. Corner::Large,
  736. Corner::Large,
  737. Corner::Large,
  738. };
  739. if (bubble.outbg) {
  740. if (bubble.attachToTop) {
  741. rounding.topRight = Corner::Small;
  742. }
  743. if (bubble.attachToBottom) {
  744. rounding.bottomRight = Corner::Small;
  745. } else if (bubble.tail) {
  746. rounding.bottomRight = Corner::Tail;
  747. }
  748. } else {
  749. if (bubble.attachToTop) {
  750. rounding.topLeft = Corner::Small;
  751. }
  752. if (bubble.attachToBottom) {
  753. rounding.bottomLeft = Corner::Small;
  754. } else if (bubble.tail) {
  755. rounding.bottomLeft = Corner::Tail;
  756. }
  757. }
  758. Ui::PaintBubble(*_p, Ui::SimpleBubble{
  759. .st = &_st,
  760. .geometry = QRect(x, bubbleTop, bubble.width, bubbleHeight),
  761. .outerWidth = _rect.width(),
  762. .outbg = bubble.outbg,
  763. .rounding = rounding,
  764. });
  765. auto trect = QRect(x, y, bubble.width, bubble.height);
  766. if (isPhoto) {
  767. trect = trect.marginsRemoved(QMargins(st::msgPadding.left(), st::mediaCaptionSkip, st::msgPadding.right(), st::msgPadding.bottom()));
  768. } else {
  769. trect = trect.marginsRemoved(st::msgPadding);
  770. }
  771. if (!bubble.replyName.isEmpty()) {
  772. trect.setY(trect.y() + st::historyReplyTop);
  773. auto bar = (bubble.outbg ? st::msgOutReplyBarColor[_palette] : st::msgInReplyBarColor[_palette]);
  774. auto rbar = style::rtlrect(
  775. trect.x(),
  776. trect.y(),
  777. trect.width(),
  778. (st::historyReplyPadding.top()
  779. + st::msgServiceNameFont->height
  780. + st::normalFont->height
  781. + st::historyReplyPadding.bottom()),
  782. _rect.width());
  783. {
  784. auto hq = PainterHighQualityEnabler(*_p);
  785. _p->setPen(Qt::NoPen);
  786. _p->setBrush(bar);
  787. const auto outline = st::messageTextStyle.blockquote.outline;
  788. const auto radius = st::messageTextStyle.blockquote.radius;
  789. _p->setOpacity(Ui::kDefaultOutline1Opacity);
  790. _p->setClipRect(rbar.x(), rbar.y(), outline, rbar.height());
  791. _p->drawRoundedRect(rbar, radius, radius);
  792. _p->setOpacity(Ui::kDefaultBgOpacity);
  793. _p->setClipRect(
  794. rbar.x() + outline,
  795. rbar.y(),
  796. rbar.width() - outline,
  797. rbar.height());
  798. _p->drawRoundedRect(rbar, radius, radius);
  799. }
  800. _p->setOpacity(1.);
  801. _p->setClipping(false);
  802. _p->setPen(bubble.outbg ? st::msgOutServiceFg[_palette] : st::msgInServiceFg[_palette]);
  803. bubble.replyName.drawLeftElided(*_p, trect.x() + st::historyReplyPadding.left(), trect.y() + st::historyReplyPadding.top(), bubble.width - st::historyReplyPadding.left() - st::historyReplyPadding.right(), _rect.width());
  804. _p->setPen(bubble.outbg ? st::historyTextOutFg[_palette] : st::historyTextInFg[_palette]);
  805. bubble.replyText.drawLeftElided(*_p, trect.x() + st::historyReplyPadding.left(), trect.y() + st::historyReplyPadding.top() + st::msgServiceNameFont->height, bubble.width - st::historyReplyPadding.left() - st::historyReplyPadding.right(), _rect.width());
  806. trect.setY(trect.y() + rbar.height() + st::historyReplyBottom);
  807. }
  808. if (!bubble.text.isEmpty()) {
  809. setTextPalette(bubble.outbg ? st::outTextPalette : st::inTextPalette);
  810. _p->setPen(bubble.outbg ? st::historyTextOutFg[_palette] : st::historyTextInFg[_palette]);
  811. _p->setFont(st::msgFont);
  812. bubble.text.draw(*_p, trect.x(), trect.y(), trect.width());
  813. } else if (!bubble.waveform.isEmpty()) {
  814. const auto &st = st::msgFileLayout;
  815. auto nameleft = x + st.padding.left() + st.thumbSize + st.thumbSkip;
  816. auto nameright = st.padding.right();
  817. auto statustop = y + st.statusTop;
  818. auto inner = style::rtlrect(x + st.padding.left(), y + st.padding.top(), st.thumbSize, st.thumbSize, _rect.width());
  819. _p->setPen(Qt::NoPen);
  820. _p->setBrush(bubble.outbg ? st::msgFileOutBg[_palette] : st::msgFileInBg[_palette]);
  821. _p->drawEllipse(inner);
  822. auto icon = ([&bubble] {
  823. return &(bubble.outbg ? st::historyFileOutPlay : st::historyFileInPlay);
  824. })();
  825. (*icon)[_palette].paintInCenter(*_p, inner);
  826. auto namewidth = x + bubble.width - nameleft - nameright;
  827. // rescale waveform by going in waveform.size * bar_count 1D grid
  828. auto active = bubble.outbg ? st::msgWaveformOutActive[_palette] : st::msgWaveformInActive[_palette];
  829. auto inactive = bubble.outbg ? st::msgWaveformOutInactive[_palette] : st::msgWaveformInInactive[_palette];
  830. auto wf_size = bubble.waveform.size();
  831. auto availw = namewidth + st::msgWaveformSkip;
  832. auto bar_count = qMin(availw / (st::msgWaveformBar + st::msgWaveformSkip), wf_size);
  833. auto max_value = 0;
  834. auto max_delta = st::msgWaveformMax - st::msgWaveformMin;
  835. auto wave_bottom = y + st::msgFileLayout.padding.top() + st::msgWaveformMax;
  836. _p->setPen(Qt::NoPen);
  837. auto norm_value = uchar(31);
  838. for (auto i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) {
  839. auto value = bubble.waveform[i];
  840. if (sum_i + bar_count >= wf_size) { // draw bar
  841. sum_i = sum_i + bar_count - wf_size;
  842. if (sum_i < (bar_count + 1) / 2) {
  843. if (max_value < value) max_value = value;
  844. }
  845. auto bar_value = ((max_value * max_delta) + ((norm_value + 1) / 2)) / (norm_value + 1);
  846. if (i >= bubble.waveactive) {
  847. _p->fillRect(nameleft + bar_x, wave_bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, inactive);
  848. } else {
  849. _p->fillRect(nameleft + bar_x, wave_bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, active);
  850. }
  851. bar_x += st::msgWaveformBar + st::msgWaveformSkip;
  852. if (sum_i < (bar_count + 1) / 2) {
  853. max_value = 0;
  854. } else {
  855. max_value = value;
  856. }
  857. } else {
  858. if (max_value < value) max_value = value;
  859. sum_i += bar_count;
  860. }
  861. }
  862. auto status = bubble.outbg ? st::mediaOutFg[_palette] : st::mediaInFg[_palette];
  863. _p->setFont(st::normalFont);
  864. _p->setPen(status);
  865. _p->drawTextLeft(nameleft, statustop, _rect.width(), bubble.wavestatus);
  866. }
  867. _p->setFont(st::msgDateFont);
  868. auto infoRight = x + bubble.width - st::msgPadding.right() + st::msgDateDelta.x();
  869. auto infoBottom = y + height - st::msgPadding.bottom() + st::msgDateDelta.y();
  870. _p->setPen(bubble.outbg ? st::msgOutDateFg[_palette] : st::msgInDateFg[_palette]);
  871. auto infoWidth = computeInfoWidth(bubble.status, bubble.date);
  872. auto dateX = infoRight - infoWidth;
  873. auto dateY = infoBottom - st::msgDateFont->height;
  874. _p->drawText(dateX, dateY + st::msgDateFont->ascent, bubble.date);
  875. auto icon = ([&bubble]() -> const style::icon * {
  876. if (bubble.status == Status::Sent) {
  877. return &st::historySentIcon;
  878. } else if (bubble.status == Status::Received) {
  879. return &st::historyReceivedIcon;
  880. }
  881. return nullptr;
  882. })();
  883. if (icon) {
  884. (*icon)[_palette].paint(*_p, QPoint(infoRight, infoBottom) + st::historySendStatePosition, _rect.width());
  885. }
  886. _historyBottom = y - (bubble.attachToTop ? st::msgMarginTopAttached : st::msgMargin.top());
  887. if (isPhoto) {
  888. auto image = bubble.photo.scaled(
  889. QSize(bubble.photoWidth, bubble.photoHeight)
  890. * style::DevicePixelRatio(),
  891. Qt::IgnoreAspectRatio,
  892. Qt::SmoothTransformation);
  893. image.setDevicePixelRatio(style::DevicePixelRatio());
  894. _p->drawImage(x, y - bubble.photoHeight, image);
  895. _historyBottom -= bubble.photoHeight;
  896. }
  897. }
  898. void Generator::paintService(QString text) {
  899. auto bubbleHeight = st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom();
  900. auto bubbleTop = _historyBottom - st::msgServiceMargin.bottom() - bubbleHeight;
  901. auto textWidth = st::msgServiceFont->width(text);
  902. auto bubbleWidth = st::msgServicePadding.left() + textWidth + st::msgServicePadding.right();
  903. auto radius = bubbleHeight / 2;
  904. _p->setPen(Qt::NoPen);
  905. _p->setBrush(st::msgServiceBg[_palette]);
  906. auto bubbleLeft = _history.x() + (_history.width() - bubbleWidth) / 2;
  907. _p->drawRoundedRect(bubbleLeft, bubbleTop, bubbleWidth, bubbleHeight, radius, radius);
  908. _p->setPen(st::msgServiceFg[_palette]);
  909. _p->setFont(st::msgServiceFont);
  910. _p->drawText(bubbleLeft + st::msgServicePadding.left(), bubbleTop + st::msgServicePadding.top() + st::msgServiceFont->ascent, text);
  911. _historyBottom = bubbleTop - st::msgServiceMargin.top();
  912. }
  913. void Generator::paintUserpic(int x, int y, Row::Type type, int index, QString letters) {
  914. const auto colorIndex = Ui::DecideColorIndex(index);
  915. const auto colors = Ui::EmptyUserpic::UserpicColor(colorIndex);
  916. auto userpic = Ui::EmptyUserpic(colors, letters);
  917. const auto size = st::defaultDialogRow.photoSize;
  918. auto image = QImage(
  919. QSize(size, size) * style::DevicePixelRatio(),
  920. QImage::Format_ARGB32_Premultiplied);
  921. image.setDevicePixelRatio(style::DevicePixelRatio());
  922. image.fill(Qt::transparent);
  923. {
  924. Painter p(&image);
  925. userpic.paintCircle(p, 0, 0, size, size);
  926. }
  927. _p->drawImage(rtl() ? (_rect.width() - x - size) : x, y, image);
  928. }
  929. void Generator::paintHistoryShadows() {
  930. _p->fillRect(_history.x() + st::lineWidth, _history.y(), _history.width() - st::lineWidth, st::lineWidth, st::shadowFg[_palette]);
  931. _p->fillRect(_history.x() + st::lineWidth, _history.y() + _history.height() - st::lineWidth, _history.width() - st::lineWidth, st::lineWidth, st::shadowFg[_palette]);
  932. _p->fillRect(_history.x(), _body.y(), st::lineWidth, _body.height(), st::shadowFg[_palette]);
  933. }
  934. void Generator::setTextPalette(const style::TextPalette &st) {
  935. _textPalette.linkFg = st.linkFg[_palette].clone();
  936. _textPalette.monoFg = st.monoFg[_palette].clone();
  937. _textPalette.spoilerFg = st.spoilerFg[_palette].clone();
  938. _textPalette.selectBg = st.selectBg[_palette].clone();
  939. _textPalette.selectFg = st.selectFg[_palette].clone();
  940. _textPalette.selectLinkFg = st.selectLinkFg[_palette].clone();
  941. _textPalette.selectMonoFg = st.selectMonoFg[_palette].clone();
  942. _textPalette.selectSpoilerFg = st.selectSpoilerFg[_palette].clone();
  943. _textPalette.selectOverlay = st.selectOverlay[_palette].clone();
  944. _p->setTextPalette(_textPalette);
  945. }
  946. void Generator::restoreTextPalette() {
  947. _p->restoreTextPalette();
  948. }
  949. } // namespace
  950. QString CachedThemePath(uint64 documentId) {
  951. return QString::fromLatin1("special://cached-%1").arg(documentId);
  952. }
  953. std::unique_ptr<Preview> PreviewFromFile(
  954. const QByteArray &bytes,
  955. const QString &filepath,
  956. const Data::CloudTheme &cloud) {
  957. auto result = std::make_unique<Preview>();
  958. auto &object = result->object;
  959. object.cloud = cloud;
  960. object.pathAbsolute = filepath.isEmpty()
  961. ? CachedThemePath(cloud.documentId)
  962. : QFileInfo(filepath).absoluteFilePath();
  963. object.pathRelative = filepath.isEmpty()
  964. ? object.pathAbsolute
  965. : QDir().relativeFilePath(filepath);
  966. const auto instance = &result->instance;
  967. const auto cache = &result->instance.cached;
  968. if (bytes.isEmpty()) {
  969. if (!LoadFromFile(filepath, instance, cache, &object.content)) {
  970. return nullptr;
  971. }
  972. } else {
  973. object.content = bytes;
  974. if (!LoadFromContent(bytes, instance, cache)) {
  975. return nullptr;
  976. }
  977. }
  978. return result;
  979. }
  980. std::unique_ptr<Preview> GeneratePreview(
  981. const QByteArray &bytes,
  982. const QString &filepath,
  983. const Data::CloudTheme &cloud,
  984. CurrentData &&data,
  985. PreviewType type) {
  986. auto result = PreviewFromFile(bytes, filepath, cloud);
  987. if (!result) {
  988. return nullptr;
  989. }
  990. result->preview = Generator(
  991. result->instance,
  992. std::move(data),
  993. type
  994. ).generate();
  995. return result;
  996. }
  997. QImage GeneratePreview(
  998. const QByteArray &bytes,
  999. const QString &filepath) {
  1000. const auto preview = GeneratePreview(
  1001. bytes,
  1002. filepath,
  1003. Data::CloudTheme(),
  1004. CurrentData{ Data::ThemeWallPaper().id() },
  1005. PreviewType::Normal);
  1006. return preview ? preview->preview : QImage();
  1007. }
  1008. int DefaultPreviewTitleHeight() {
  1009. return st::defaultWindowTitle.height;
  1010. }
  1011. void DefaultPreviewWindowTitle(Painter &p, const style::palette &palette, QRect body, int outerWidth) {
  1012. auto titleRect = QRect(body.x(), body.y() - st::defaultWindowTitle.height, body.width(), st::defaultWindowTitle.height);
  1013. p.fillRect(titleRect, QColor(0, 0, 0));
  1014. p.fillRect(titleRect, st::titleBgActive[palette]);
  1015. auto right = st::defaultWindowTitle.close.width;
  1016. st::defaultWindowTitle.close.icon[palette].paint(p, titleRect.x() + titleRect.width() - right + st::defaultWindowTitle.close.iconPosition.x(), titleRect.y() + st::windowTitleButtonClose.iconPosition.y(), outerWidth);
  1017. right += st::defaultWindowTitle.maximize.width;
  1018. st::defaultWindowTitle.maximize.icon[palette].paint(p, titleRect.x() + titleRect.width() - right + st::defaultWindowTitle.maximize.iconPosition.x(), titleRect.y() + st::defaultWindowTitle.maximize.iconPosition.y(), outerWidth);
  1019. right += st::defaultWindowTitle.minimize.width;
  1020. st::defaultWindowTitle.minimize.icon[palette].paint(p, titleRect.x() + titleRect.width() - right + st::defaultWindowTitle.minimize.iconPosition.x(), titleRect.y() + st::defaultWindowTitle.minimize.iconPosition.y(), outerWidth);
  1021. p.fillRect(titleRect.x(), titleRect.y() + titleRect.height() - st::lineWidth, titleRect.width(), st::lineWidth, st::titleShadow[palette]);
  1022. }
  1023. void DefaultPreviewWindowFramePaint(QImage &preview, const style::palette &palette, QRect body, int outerWidth) {
  1024. auto mask = QImage(
  1025. st::windowShadow.size() * style::DevicePixelRatio(),
  1026. QImage::Format_ARGB32_Premultiplied);
  1027. mask.setDevicePixelRatio(style::DevicePixelRatio());
  1028. {
  1029. Painter p(&mask);
  1030. p.setCompositionMode(QPainter::CompositionMode_Source);
  1031. st::windowShadow.paint(p, 0, 0, st::windowShadow.width(), QColor(0, 0, 0));
  1032. }
  1033. auto maxSize = 0;
  1034. auto currentInt = static_cast<uint32>(0);
  1035. auto lastLineInts = reinterpret_cast<const uint32*>(mask.constBits() + (mask.height() - 1) * mask.bytesPerLine());
  1036. for (auto end = lastLineInts + mask.width(); lastLineInts != end; ++lastLineInts) {
  1037. if (*lastLineInts < currentInt) {
  1038. break;
  1039. }
  1040. currentInt = *lastLineInts;
  1041. ++maxSize;
  1042. }
  1043. if (maxSize % style::DevicePixelRatio()) {
  1044. maxSize -= (maxSize % style::DevicePixelRatio());
  1045. }
  1046. auto size = maxSize / style::DevicePixelRatio();
  1047. auto bottom = size;
  1048. auto left = size - st::windowShadowShift;
  1049. auto right = left;
  1050. auto top = size - 2 * st::windowShadowShift;
  1051. auto sprite = st::windowShadow[palette];
  1052. auto topLeft = QImage(
  1053. sprite.size() * style::DevicePixelRatio(),
  1054. QImage::Format_ARGB32_Premultiplied);
  1055. topLeft.setDevicePixelRatio(style::DevicePixelRatio());
  1056. {
  1057. Painter p(&topLeft);
  1058. p.setCompositionMode(QPainter::CompositionMode_Source);
  1059. sprite.paint(p, 0, 0, sprite.width());
  1060. }
  1061. auto width = sprite.width();
  1062. auto height = sprite.height();
  1063. auto topRight = topLeft.mirrored(true, false);
  1064. auto bottomRight = topLeft.mirrored(true, true);
  1065. auto bottomLeft = topLeft.mirrored(false, true);
  1066. Painter p(&preview);
  1067. DefaultPreviewWindowTitle(p, palette, body, outerWidth);
  1068. auto inner = QRect(
  1069. body.x(),
  1070. body.y() - st::defaultWindowTitle.height,
  1071. body.width(),
  1072. body.height() + st::defaultWindowTitle.height);
  1073. p.setClipRegion(QRegion(inner + Margins(size)) - inner);
  1074. p.drawImage(inner.x() - left, inner.y() - top, topLeft);
  1075. p.drawImage(
  1076. inner.x() + inner.width() + right - width,
  1077. inner.y() - top,
  1078. topRight);
  1079. p.drawImage(
  1080. inner.x() + inner.width() + right - width,
  1081. inner.y() + inner.height() + bottom - height,
  1082. bottomRight);
  1083. p.drawImage(
  1084. inner.x() - left,
  1085. inner.y() + inner.height() + bottom - height,
  1086. bottomLeft);
  1087. p.drawImage(
  1088. QRect(
  1089. inner.x() - left,
  1090. inner.y() - top + height,
  1091. left,
  1092. top + inner.height() + bottom - 2 * height),
  1093. topLeft,
  1094. QRect(
  1095. 0,
  1096. topLeft.height() - style::DevicePixelRatio(),
  1097. left * style::DevicePixelRatio(),
  1098. style::DevicePixelRatio()));
  1099. p.drawImage(
  1100. QRect(
  1101. inner.x() - left + width,
  1102. inner.y() - top,
  1103. left + inner.width() + right - 2 * width,
  1104. top),
  1105. topLeft,
  1106. QRect(
  1107. topLeft.width() - style::DevicePixelRatio(),
  1108. 0,
  1109. style::DevicePixelRatio(),
  1110. top * style::DevicePixelRatio()));
  1111. p.drawImage(
  1112. QRect(
  1113. inner.x() + inner.width(),
  1114. inner.y() - top + height,
  1115. right,
  1116. top + inner.height() + bottom - 2 * height),
  1117. topRight,
  1118. QRect(
  1119. topRight.width() - right * style::DevicePixelRatio(),
  1120. topRight.height() - style::DevicePixelRatio(),
  1121. right * style::DevicePixelRatio(),
  1122. style::DevicePixelRatio()));
  1123. p.drawImage(
  1124. QRect(
  1125. inner.x() - left + width,
  1126. inner.y() + inner.height(),
  1127. left + inner.width() + right - 2 * width,
  1128. bottom),
  1129. bottomRight,
  1130. QRect(
  1131. 0,
  1132. bottomRight.height() - bottom * style::DevicePixelRatio(),
  1133. style::DevicePixelRatio(),
  1134. bottom * style::DevicePixelRatio()));
  1135. }
  1136. } // namespace Theme
  1137. } // namespace Window