message_bubble.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  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/chat/message_bubble.h"
  8. #include "ui/cached_round_corners.h"
  9. #include "ui/image/image_prepare.h"
  10. #include "ui/chat/chat_style.h"
  11. #include "styles/style_chat.h"
  12. namespace Ui {
  13. namespace {
  14. using Corner = BubbleCornerRounding;
  15. template <
  16. typename FillBg, // fillBg(QRect rect)
  17. typename FillSh, // fillSh(QRect rect)
  18. typename FillCorner, // fillCorner(int x, int y, int index, Corner size)
  19. typename PaintTail> // paintTail(QPoint bottomPosition) -> tailWidth
  20. void PaintBubbleGeneric(
  21. const SimpleBubble &args,
  22. FillBg &&fillBg,
  23. FillSh &&fillSh,
  24. FillCorner &&fillCorner,
  25. PaintTail &&paintTail) {
  26. using namespace Images;
  27. const auto topLeft = args.rounding.topLeft;
  28. const auto topRight = args.rounding.topRight;
  29. const auto bottomWithTailLeft = args.rounding.bottomLeft;
  30. const auto bottomWithTailRight = args.rounding.bottomRight;
  31. if (topLeft == Corner::None
  32. && topRight == Corner::None
  33. && bottomWithTailLeft == Corner::None
  34. && bottomWithTailRight == Corner::None) {
  35. fillBg(args.geometry);
  36. return;
  37. }
  38. const auto bottomLeft = (bottomWithTailLeft == Corner::Tail)
  39. ? Corner::None
  40. : bottomWithTailLeft;
  41. const auto bottomRight = (bottomWithTailRight == Corner::Tail)
  42. ? Corner::None
  43. : bottomWithTailRight;
  44. const auto rect = args.geometry;
  45. const auto small = BubbleRadiusSmall();
  46. const auto large = BubbleRadiusLarge();
  47. const auto cornerSize = [&](Corner corner) {
  48. return (corner == Corner::Large)
  49. ? large
  50. : (corner == Corner::Small)
  51. ? small
  52. : 0;
  53. };
  54. const auto verticalSkip = [&](Corner left, Corner right) {
  55. return std::max(cornerSize(left), cornerSize(right));
  56. };
  57. const auto top = verticalSkip(topLeft, topRight);
  58. const auto bottom = verticalSkip(bottomLeft, bottomRight);
  59. if (top) {
  60. const auto left = cornerSize(topLeft);
  61. const auto right = cornerSize(topRight);
  62. if (left) {
  63. fillCorner(rect.left(), rect.top(), kTopLeft, topLeft);
  64. if (const auto add = top - left) {
  65. fillBg({ rect.left(), rect.top() + left, left, add });
  66. }
  67. }
  68. if (const auto fill = rect.width() - left - right; fill > 0) {
  69. fillBg({ rect.left() + left, rect.top(), fill, top });
  70. }
  71. if (right) {
  72. fillCorner(
  73. rect.left() + rect.width() - right,
  74. rect.top(),
  75. kTopRight,
  76. topRight);
  77. if (const auto add = top - right) {
  78. fillBg({
  79. rect.left() + rect.width() - right,
  80. rect.top() + right,
  81. right,
  82. add,
  83. });
  84. }
  85. }
  86. }
  87. if (const auto fill = rect.height() - top - bottom; fill > 0) {
  88. fillBg({ rect.left(), rect.top() + top, rect.width(), fill });
  89. }
  90. if (bottom) {
  91. const auto left = cornerSize(bottomLeft);
  92. const auto right = cornerSize(bottomRight);
  93. if (left) {
  94. fillCorner(
  95. rect.left(),
  96. rect.top() + rect.height() - left,
  97. kBottomLeft,
  98. bottomLeft);
  99. if (const auto add = bottom - left) {
  100. fillBg({
  101. rect.left(),
  102. rect.top() + rect.height() - bottom,
  103. left,
  104. add,
  105. });
  106. }
  107. }
  108. if (const auto fill = rect.width() - left - right; fill > 0) {
  109. fillBg({
  110. rect.left() + left,
  111. rect.top() + rect.height() - bottom,
  112. fill,
  113. bottom,
  114. });
  115. }
  116. if (right) {
  117. fillCorner(
  118. rect.left() + rect.width() - right,
  119. rect.top() + rect.height() - right,
  120. kBottomRight,
  121. bottomRight);
  122. if (const auto add = bottom - right) {
  123. fillBg({
  124. rect.left() + rect.width() - right,
  125. rect.top() + rect.height() - bottom,
  126. right,
  127. add,
  128. });
  129. }
  130. }
  131. }
  132. const auto leftTail = (bottomWithTailLeft == Corner::Tail)
  133. ? paintTail({ rect.x(), rect.y() + rect.height() })
  134. : 0;
  135. const auto rightTail = (bottomWithTailRight == Corner::Tail)
  136. ? paintTail({ rect.x() + rect.width(), rect.y() + rect.height() })
  137. : 0;
  138. if (!args.shadowed) {
  139. return;
  140. }
  141. const auto shLeft = rect.x() + cornerSize(bottomLeft) - leftTail;
  142. const auto shWidth = rect.x()
  143. + rect.width()
  144. - cornerSize(bottomRight)
  145. + rightTail
  146. - shLeft;
  147. if (shWidth > 0) {
  148. fillSh({ shLeft, rect.y() + rect.height(), shWidth, st::msgShadow });
  149. }
  150. }
  151. void PaintPatternBubble(QPainter &p, const SimpleBubble &args) {
  152. const auto opacity = args.st->msgOutBg()->c.alphaF();
  153. const auto shadowOpacity = opacity * args.st->msgOutShadow()->c.alphaF();
  154. const auto pattern = args.pattern;
  155. const auto &tail = (args.rounding.bottomRight == Corner::Tail)
  156. ? pattern->tailRight
  157. : pattern->tailLeft;
  158. const auto tailShift = (args.rounding.bottomRight == Corner::Tail
  159. ? QPoint(0, tail.height())
  160. : QPoint(tail.width(), tail.height())) / int(tail.devicePixelRatio());
  161. const auto fillBg = [&](const QRect &rect) {
  162. const auto fill = rect.intersected(args.patternViewport);
  163. if (!fill.isEmpty()) {
  164. PaintPatternBubblePart(
  165. p,
  166. args.patternViewport,
  167. pattern->pixmap,
  168. fill);
  169. }
  170. };
  171. const auto fillSh = [&](const QRect &rect) {
  172. p.setOpacity(shadowOpacity);
  173. fillBg(rect);
  174. p.setOpacity(opacity);
  175. };
  176. const auto fillPattern = [&](
  177. int x,
  178. int y,
  179. const QImage &mask,
  180. QImage &cache) {
  181. PaintPatternBubblePart(
  182. p,
  183. args.patternViewport,
  184. pattern->pixmap,
  185. QRect(QPoint(x, y), mask.size() / int(mask.devicePixelRatio())),
  186. mask,
  187. cache);
  188. };
  189. const auto fillCorner = [&](int x, int y, int index, Corner size) {
  190. auto &corner = (size == Corner::Large)
  191. ? pattern->cornersLarge[index]
  192. : pattern->cornersSmall[index];
  193. auto &cache = (size == Corner::Large)
  194. ? (index < 2
  195. ? pattern->cornerTopLargeCache
  196. : pattern->cornerBottomLargeCache)
  197. : (index < 2
  198. ? pattern->cornerTopSmallCache
  199. : pattern->cornerBottomSmallCache);
  200. fillPattern(x, y, corner, cache);
  201. };
  202. const auto paintTail = [&](QPoint bottomPosition) {
  203. const auto position = bottomPosition - tailShift;
  204. fillPattern(position.x(), position.y(), tail, pattern->tailCache);
  205. return tail.width() / int(tail.devicePixelRatio());
  206. };
  207. p.setOpacity(opacity);
  208. PaintBubbleGeneric(args, fillBg, fillSh, fillCorner, paintTail);
  209. p.setOpacity(1.);
  210. }
  211. void PaintSolidBubble(QPainter &p, const SimpleBubble &args) {
  212. const auto &st = args.st->messageStyle(args.outbg, args.selected);
  213. const auto &bg = st.msgBg;
  214. const auto sh = (args.rounding.bottomRight == Corner::None)
  215. ? nullptr
  216. : &st.msgShadow;
  217. const auto &tail = (args.rounding.bottomRight == Corner::Tail)
  218. ? st.tailRight
  219. : st.tailLeft;
  220. const auto tailShift = (args.rounding.bottomRight == Corner::Tail)
  221. ? QPoint(0, tail.height())
  222. : QPoint(tail.width(), tail.height());
  223. PaintBubbleGeneric(args, [&](const QRect &rect) {
  224. p.fillRect(rect, bg);
  225. }, [&](const QRect &rect) {
  226. p.fillRect(rect, *sh);
  227. }, [&](int x, int y, int index, Corner size) {
  228. auto &corners = (size == Corner::Large)
  229. ? st.msgBgCornersLarge
  230. : st.msgBgCornersSmall;
  231. p.drawPixmap(x, y, corners.p[index]);
  232. }, [&](const QPoint &bottomPosition) {
  233. tail.paint(p, bottomPosition - tailShift, args.outerWidth);
  234. return tail.width();
  235. });
  236. }
  237. } // namespace
  238. std::unique_ptr<BubblePattern> PrepareBubblePattern(
  239. not_null<const style::palette*> st) {
  240. auto result = std::make_unique<Ui::BubblePattern>();
  241. result->cornersSmall = Images::CornersMask(BubbleRadiusSmall());
  242. result->cornersLarge = Images::CornersMask(BubbleRadiusLarge());
  243. const auto addShadow = [&](QImage &bottomCorner) {
  244. auto result = QImage(
  245. bottomCorner.width(),
  246. (bottomCorner.height()
  247. + st::msgShadow * int(bottomCorner.devicePixelRatio())),
  248. QImage::Format_ARGB32_Premultiplied);
  249. result.fill(Qt::transparent);
  250. result.setDevicePixelRatio(bottomCorner.devicePixelRatio());
  251. auto p = QPainter(&result);
  252. p.setOpacity(st->msgInShadow()->c.alphaF());
  253. p.drawImage(0, st::msgShadow, bottomCorner);
  254. p.setOpacity(1.);
  255. p.drawImage(0, 0, bottomCorner);
  256. p.end();
  257. bottomCorner = std::move(result);
  258. };
  259. addShadow(result->cornersSmall[2]);
  260. addShadow(result->cornersSmall[3]);
  261. result->cornerTopSmallCache = QImage(
  262. result->cornersSmall[0].size(),
  263. QImage::Format_ARGB32_Premultiplied);
  264. result->cornerTopLargeCache = QImage(
  265. result->cornersLarge[0].size(),
  266. QImage::Format_ARGB32_Premultiplied);
  267. result->cornerBottomSmallCache = QImage(
  268. result->cornersSmall[2].size(),
  269. QImage::Format_ARGB32_Premultiplied);
  270. result->cornerBottomLargeCache = QImage(
  271. result->cornersLarge[2].size(),
  272. QImage::Format_ARGB32_Premultiplied);
  273. return result;
  274. }
  275. void FinishBubblePatternOnMain(not_null<BubblePattern*> pattern) {
  276. pattern->tailLeft = st::historyBubbleTailOutLeft.instance(Qt::white);
  277. pattern->tailRight = st::historyBubbleTailOutRight.instance(Qt::white);
  278. pattern->tailCache = QImage(
  279. pattern->tailLeft.size(),
  280. QImage::Format_ARGB32_Premultiplied);
  281. }
  282. void PaintBubble(QPainter &p, const SimpleBubble &args) {
  283. if (!args.selected
  284. && args.outbg
  285. && args.pattern
  286. && !args.patternViewport.isEmpty()
  287. && !args.pattern->pixmap.size().isEmpty()) {
  288. PaintPatternBubble(p, args);
  289. } else {
  290. PaintSolidBubble(p, args);
  291. }
  292. }
  293. void PaintBubble(QPainter &p, const ComplexBubble &args) {
  294. if (args.selection.empty()) {
  295. PaintBubble(p, args.simple);
  296. return;
  297. }
  298. const auto rect = args.simple.geometry;
  299. const auto left = rect.x();
  300. const auto width = rect.width();
  301. const auto top = rect.y();
  302. const auto bottom = top + rect.height();
  303. const auto paintOne = [&](
  304. QRect geometry,
  305. bool selected,
  306. bool fromTop,
  307. bool tillBottom) {
  308. auto simple = args.simple;
  309. simple.geometry = geometry;
  310. simple.selected = selected;
  311. if (!fromTop) {
  312. simple.rounding.topLeft
  313. = simple.rounding.topRight
  314. = Corner::None;
  315. }
  316. if (!tillBottom) {
  317. simple.rounding.bottomLeft
  318. = simple.rounding.bottomRight
  319. = Corner::None;
  320. simple.shadowed = false;
  321. }
  322. PaintBubble(p, simple);
  323. };
  324. auto from = top;
  325. for (const auto &selected : args.selection) {
  326. if (selected.top > from) {
  327. paintOne(
  328. QRect(left, from, width, selected.top - from),
  329. false,
  330. (from <= top),
  331. false);
  332. }
  333. paintOne(
  334. QRect(left, selected.top, width, selected.height),
  335. true,
  336. (selected.top <= top),
  337. (selected.top + selected.height >= bottom));
  338. from = selected.top + selected.height;
  339. }
  340. if (from < bottom) {
  341. paintOne(
  342. QRect(left, from, width, bottom - from),
  343. false,
  344. false,
  345. true);
  346. }
  347. }
  348. void PaintPatternBubblePart(
  349. QPainter &p,
  350. const QRect &viewport,
  351. const QPixmap &pixmap,
  352. const QRect &target) {
  353. const auto factor = pixmap.devicePixelRatio();
  354. if (viewport.size() * factor == pixmap.size()) {
  355. const auto fill = target.intersected(viewport);
  356. if (fill.isEmpty()) {
  357. return;
  358. }
  359. p.drawPixmap(fill, pixmap, QRect(
  360. (fill.topLeft() - viewport.topLeft()) * factor,
  361. fill.size() * factor));
  362. } else {
  363. const auto to = viewport;
  364. const auto from = QRect(QPoint(), pixmap.size());
  365. const auto deviceRect = QRect(
  366. QPoint(),
  367. QSize(p.device()->width(), p.device()->height()));
  368. const auto clip = (target != deviceRect);
  369. if (clip) {
  370. p.setClipRect(target);
  371. }
  372. p.drawPixmap(to, pixmap, from);
  373. if (clip) {
  374. p.setClipping(false);
  375. }
  376. }
  377. }
  378. void PaintPatternBubblePart(
  379. QPainter &p,
  380. const QRect &viewport,
  381. const QPixmap &pixmap,
  382. const QRect &target,
  383. const QImage &mask,
  384. QImage &cache) {
  385. Expects(mask.bytesPerLine() == mask.width() * 4);
  386. Expects(mask.format() == QImage::Format_ARGB32_Premultiplied);
  387. if (cache.size() != mask.size()) {
  388. cache = QImage(
  389. mask.size(),
  390. QImage::Format_ARGB32_Premultiplied);
  391. }
  392. cache.setDevicePixelRatio(mask.devicePixelRatio());
  393. Assert(cache.bytesPerLine() == cache.width() * 4);
  394. memcpy(cache.bits(), mask.constBits(), mask.sizeInBytes());
  395. auto q = QPainter(&cache);
  396. q.setCompositionMode(QPainter::CompositionMode_SourceIn);
  397. PaintPatternBubblePart(
  398. q,
  399. viewport.translated(-target.topLeft()),
  400. pixmap,
  401. QRect(QPoint(), cache.size() / int(cache.devicePixelRatio())));
  402. q.end();
  403. p.drawImage(target, cache);
  404. }
  405. void PaintPatternBubblePart(
  406. QPainter &p,
  407. const QRect &viewport,
  408. const QPixmap &pixmap,
  409. const QRect &target,
  410. Fn<void(QPainter&)> paintContent,
  411. QImage &cache) {
  412. Expects(paintContent != nullptr);
  413. const auto targetOrigin = target.topLeft();
  414. const auto targetSize = target.size();
  415. if (cache.size() != targetSize * style::DevicePixelRatio()) {
  416. cache = QImage(
  417. target.size() * style::DevicePixelRatio(),
  418. QImage::Format_ARGB32_Premultiplied);
  419. cache.setDevicePixelRatio(style::DevicePixelRatio());
  420. }
  421. cache.fill(Qt::transparent);
  422. auto q = QPainter(&cache);
  423. q.translate(-targetOrigin);
  424. paintContent(q);
  425. q.translate(targetOrigin);
  426. q.setCompositionMode(QPainter::CompositionMode_SourceIn);
  427. PaintPatternBubblePart(
  428. q,
  429. viewport.translated(-targetOrigin),
  430. pixmap,
  431. QRect(QPoint(), targetSize));
  432. q.end();
  433. p.drawImage(target, cache);
  434. }
  435. } // namespace Ui