dialogs_row.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763
  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 "dialogs/dialogs_row.h"
  8. #include "ui/chat/chat_theme.h" // CountAverageColor.
  9. #include "ui/color_contrast.h"
  10. #include "ui/effects/credits_graphics.h"
  11. #include "ui/effects/outline_segments.h"
  12. #include "ui/effects/ripple_animation.h"
  13. #include "ui/image/image_prepare.h"
  14. #include "ui/text/format_values.h"
  15. #include "ui/text/text_options.h"
  16. #include "ui/text/text_utilities.h"
  17. #include "ui/painter.h"
  18. #include "dialogs/dialogs_entry.h"
  19. #include "dialogs/ui/dialogs_video_userpic.h"
  20. #include "dialogs/ui/dialogs_layout.h"
  21. #include "data/data_folder.h"
  22. #include "data/data_forum.h"
  23. #include "data/data_session.h"
  24. #include "data/data_stories.h"
  25. #include "data/data_peer_values.h"
  26. #include "data/data_user.h"
  27. #include "history/history.h"
  28. #include "history/history_item.h"
  29. #include "lang/lang_keys.h"
  30. #include "base/unixtime.h"
  31. #include "styles/style_dialogs.h"
  32. namespace Dialogs {
  33. namespace {
  34. constexpr auto kTopLayer = 2;
  35. constexpr auto kBottomLayer = 1;
  36. constexpr auto kNoneLayer = 0;
  37. constexpr auto kBlurRadius = 24;
  38. [[nodiscard]] const QPainterPath &SubscriptionOutlinePath() {
  39. static auto path = QPainterPath();
  40. if (!path.isEmpty()) {
  41. return path;
  42. }
  43. const auto scaledMoveTo = [&](float64 x, float64 y) {
  44. path.moveTo(style::ConvertFloatScale(x), style::ConvertFloatScale(y));
  45. };
  46. const auto scaledLineTo = [&](float64 x, float64 y) {
  47. path.lineTo(style::ConvertFloatScale(x), style::ConvertFloatScale(y));
  48. };
  49. const auto scaledCubicTo = [&](
  50. float64 x1,
  51. float64 y1,
  52. float64 x2,
  53. float64 y2,
  54. float64 x3,
  55. float64 y3) {
  56. path.cubicTo(
  57. style::ConvertFloatScale(x1),
  58. style::ConvertFloatScale(y1),
  59. style::ConvertFloatScale(x2),
  60. style::ConvertFloatScale(y2),
  61. style::ConvertFloatScale(x3),
  62. style::ConvertFloatScale(y3));
  63. };
  64. const auto scaledTranslate = [&](float64 x, float64 y) {
  65. path.translate(
  66. style::ConvertFloatScale(x),
  67. style::ConvertFloatScale(y));
  68. };
  69. scaledMoveTo(42.3009, 18.3345);
  70. scaledLineTo(44.3285, 14.1203);
  71. scaledCubicTo(44.6152, 13.6549, 45.7858, 13.3542, 46.1909, 13.5523);
  72. scaledCubicTo(46.3355, 13.6044, 47.0064, 13.7541, 47.3833, 14.5053);
  73. scaledLineTo(49.3924 * 1.0071, 18.4206 * 0.9905);
  74. // 49.5459 * 1.007, 18.7336 * 0.9897.
  75. scaledCubicTo(49.8927213, 18.5406439, 52.5473, 18.8491, 53.3141, 18.8789);
  76. scaledCubicTo(53.6484, 18.8441, 55.8914, 20.0065, 54.3752, 20.7818);
  77. scaledCubicTo(54.1725, 20.8744, 41.3467, 31.3217, 41.3467, 31.3217);
  78. scaledCubicTo(40.7918, 31.5944, 41.2661, 31.4116, 40.8968, 30.9483);
  79. scaledCubicTo(39.9809, 30.3111, 40.0577, 25.4542, 40.1925, 25.5408);
  80. scaledCubicTo(39.9835, 25.6454, 38.4545, 22.9776, 37.8121, 22.3477);
  81. scaledLineTo(37.3236, 21.4448);
  82. scaledCubicTo(37.0943, 20.8845, 37.2524, 20.4742, 37.4164, 19.7765);
  83. scaledCubicTo(37.4703, 19.4582, 38.1756, 19.0759, 38.4504, 19.0422);
  84. scaledLineTo(41.6566, 18.6449);
  85. scaledCubicTo(41.5344, 18.6041, 42.2622, 18.6087, 42.3009, 18.3345);
  86. scaledTranslate(-42.3009, -18.3345);
  87. scaledTranslate(1.2, 0.4);
  88. return path;
  89. }
  90. [[nodiscard]] const QImage &SubscriptionIcon() {
  91. static auto starImage = QImage();
  92. if (!starImage.isNull()) {
  93. return starImage;
  94. }
  95. starImage = Ui::GenerateStars(st::dialogsSubscriptionBadgeSize, 1);
  96. return starImage;
  97. }
  98. [[nodiscard]] QImage CornerBadgeTTL(
  99. not_null<PeerData*> peer,
  100. Ui::PeerUserpicView &view,
  101. int photoSize) {
  102. const auto ttl = peer->messagesTTL();
  103. if (!ttl) {
  104. return QImage();
  105. }
  106. const auto ratio = style::DevicePixelRatio();
  107. const auto fullSize = photoSize;
  108. const auto partRect = CornerBadgeTTLRect(fullSize);
  109. const auto &partSize = partRect.width();
  110. const auto partSkip = fullSize - partSize;
  111. auto result = Images::Circle(BlurredDarkenedPart(
  112. PeerData::GenerateUserpicImage(peer, view, fullSize * ratio, 0),
  113. QRect(
  114. QPoint(partSkip, partSkip) * ratio,
  115. QSize(partSize, partSize) * ratio)));
  116. result.setDevicePixelRatio(ratio);
  117. auto q = QPainter(&result);
  118. PainterHighQualityEnabler hq(q);
  119. const auto innerRect = QRect(QPoint(), partRect.size())
  120. - st::dialogsTTLBadgeInnerMargins;
  121. const auto ttlText = Ui::FormatTTLTiny(ttl);
  122. q.setFont(st::dialogsScamFont);
  123. q.setPen(st::premiumButtonFg);
  124. q.drawText(
  125. innerRect,
  126. (ttlText.size() > 2) ? ttlText.mid(0, 2) : ttlText,
  127. style::al_center);
  128. constexpr auto kPenWidth = 1.5;
  129. const auto penWidth = style::ConvertScaleExact(kPenWidth);
  130. auto pen = QPen(st::premiumButtonFg);
  131. pen.setJoinStyle(Qt::RoundJoin);
  132. pen.setCapStyle(Qt::RoundCap);
  133. pen.setWidthF(penWidth);
  134. q.setPen(pen);
  135. q.setBrush(Qt::NoBrush);
  136. q.drawArc(innerRect, arc::kQuarterLength, arc::kHalfLength);
  137. q.setClipRect(innerRect
  138. - QMargins(innerRect.width() / 2, -penWidth, -penWidth, -penWidth));
  139. pen.setStyle(Qt::DotLine);
  140. q.setPen(pen);
  141. q.drawEllipse(innerRect);
  142. return result;
  143. }
  144. } // namespace
  145. QRect CornerBadgeTTLRect(int photoSize) {
  146. const auto &partSize = st::dialogsTTLBadgeSize;
  147. return QRect(
  148. photoSize - partSize + st::dialogsTTLBadgeSkip.x(),
  149. photoSize - partSize + st::dialogsTTLBadgeSkip.y(),
  150. partSize,
  151. partSize);
  152. }
  153. QImage BlurredDarkenedPart(QImage image, QRect part) {
  154. auto blurred = Images::BlurLargeImage(
  155. std::move(image),
  156. kBlurRadius).copy(part);
  157. constexpr auto kMinAcceptableContrast = 4.5;
  158. const auto averageColor = Ui::CountAverageColor(blurred);
  159. const auto contrast = Ui::CountContrast(
  160. averageColor,
  161. st::premiumButtonFg->c);
  162. if (contrast < kMinAcceptableContrast) {
  163. constexpr auto kDarkerBy = 0.2;
  164. auto painterPart = QPainter(&blurred);
  165. painterPart.setOpacity(kDarkerBy);
  166. painterPart.fillRect(QRect(QPoint(), part.size()), Qt::black);
  167. }
  168. blurred.setDevicePixelRatio(image.devicePixelRatio());
  169. return blurred;
  170. }
  171. Row::CornerLayersManager::CornerLayersManager() = default;
  172. bool Row::CornerLayersManager::isSameLayer(Layer layer) const {
  173. return isFinished() && (_nextLayer == layer);
  174. }
  175. void Row::CornerLayersManager::setLayer(
  176. Layer layer,
  177. Fn<void()> updateCallback) {
  178. if (_nextLayer == layer) {
  179. return;
  180. }
  181. _lastFrameShown = false;
  182. _prevLayer = _nextLayer;
  183. _nextLayer = layer;
  184. if (_animation.animating()) {
  185. _animation.change(
  186. 1.,
  187. st::dialogsOnlineBadgeDuration * (1. - _animation.value(1.)));
  188. } else if (updateCallback) {
  189. _animation.start(
  190. std::move(updateCallback),
  191. 0.,
  192. 1.,
  193. st::dialogsOnlineBadgeDuration);
  194. }
  195. }
  196. float64 Row::CornerLayersManager::progressForLayer(Layer layer) const {
  197. return (_nextLayer == layer)
  198. ? progress()
  199. : (_prevLayer == layer)
  200. ? (1. - progress())
  201. : 0.;
  202. }
  203. float64 Row::CornerLayersManager::progress() const {
  204. return _animation.value(1.);
  205. }
  206. bool Row::CornerLayersManager::isFinished() const {
  207. return (progress() == 1.) && _lastFrameShown;
  208. }
  209. void Row::CornerLayersManager::markFrameShown() {
  210. if (progress() == 1.) {
  211. _lastFrameShown = true;
  212. }
  213. }
  214. bool Row::CornerLayersManager::isDisplayedNone() const {
  215. return (progress() == 1.) && (_nextLayer == 0);
  216. }
  217. BasicRow::BasicRow() = default;
  218. BasicRow::~BasicRow() = default;
  219. void BasicRow::addRipple(
  220. QPoint origin,
  221. QSize size,
  222. Fn<void()> updateCallback) {
  223. if (!_ripple) {
  224. addRippleWithMask(
  225. origin,
  226. Ui::RippleAnimation::RectMask(size),
  227. std::move(updateCallback));
  228. } else {
  229. _ripple->add(origin);
  230. }
  231. }
  232. void BasicRow::addRippleWithMask(
  233. QPoint origin,
  234. QImage mask,
  235. Fn<void()> updateCallback) {
  236. _ripple = std::make_unique<Ui::RippleAnimation>(
  237. st::dialogsRipple,
  238. std::move(mask),
  239. std::move(updateCallback));
  240. _ripple->add(origin);
  241. }
  242. void BasicRow::clearRipple() {
  243. _ripple = nullptr;
  244. }
  245. void BasicRow::stopLastRipple() {
  246. if (_ripple) {
  247. _ripple->lastStop();
  248. }
  249. }
  250. void BasicRow::paintRipple(
  251. QPainter &p,
  252. int x,
  253. int y,
  254. int outerWidth,
  255. const QColor *colorOverride) const {
  256. if (_ripple) {
  257. _ripple->paint(p, x, y, outerWidth, colorOverride);
  258. if (_ripple->empty()) {
  259. _ripple.reset();
  260. }
  261. }
  262. }
  263. void BasicRow::paintUserpic(
  264. Painter &p,
  265. not_null<Entry*> entry,
  266. PeerData *peer,
  267. Ui::VideoUserpic *videoUserpic,
  268. const Ui::PaintContext &context,
  269. bool hasUnreadBadgesAbove) const {
  270. PaintUserpic(p, entry, peer, videoUserpic, _userpic, context);
  271. }
  272. Row::Row(Key key, int index, int top) : _id(key), _top(top), _index(index) {
  273. if (const auto history = key.history()) {
  274. updateCornerBadgeShown(history->peer);
  275. }
  276. }
  277. Row::~Row() {
  278. clearTopicJumpRipple();
  279. }
  280. void Row::recountHeight(float64 narrowRatio, FilterId filterId) {
  281. if (const auto history = _id.history()) {
  282. const auto hasTags = _id.entry()->hasChatsFilterTags(filterId);
  283. _height = history->isForum()
  284. ? anim::interpolate(
  285. hasTags
  286. ? st::taggedForumDialogRow.height
  287. : st::forumDialogRow.height,
  288. st::defaultDialogRow.height,
  289. narrowRatio)
  290. : hasTags
  291. ? anim::interpolate(
  292. st::taggedDialogRow.height,
  293. st::defaultDialogRow.height,
  294. narrowRatio)
  295. : st::defaultDialogRow.height;
  296. } else if (_id.folder()) {
  297. _height = st::defaultDialogRow.height;
  298. } else if (_id.topic()) {
  299. _height = st::forumTopicRow.height;
  300. } else {
  301. _height = st::defaultDialogRow.height;
  302. }
  303. }
  304. uint64 Row::sortKey(FilterId filterId) const {
  305. return _id.entry()->sortKeyInChatList(filterId);
  306. }
  307. void Row::setCornerBadgeShown(
  308. CornerLayersManager::Layer nextLayer,
  309. Fn<void()> updateCallback) const {
  310. const auto cornerBadgeShown = (nextLayer ? 1 : 0);
  311. if (_cornerBadgeShown == cornerBadgeShown) {
  312. if (!cornerBadgeShown) {
  313. return;
  314. } else if (_cornerBadgeUserpic
  315. && _cornerBadgeUserpic->layersManager.isSameLayer(nextLayer)) {
  316. return;
  317. }
  318. }
  319. const_cast<Row*>(this)->_cornerBadgeShown = cornerBadgeShown;
  320. ensureCornerBadgeUserpic();
  321. _cornerBadgeUserpic->layersManager.setLayer(
  322. nextLayer,
  323. std::move(updateCallback));
  324. if (!_cornerBadgeShown
  325. && _cornerBadgeUserpic
  326. && _cornerBadgeUserpic->layersManager.isDisplayedNone()) {
  327. _cornerBadgeUserpic = nullptr;
  328. }
  329. }
  330. void Row::updateCornerBadgeShown(
  331. not_null<PeerData*> peer,
  332. Fn<void()> updateCallback,
  333. bool hasUnreadBadgesAbove) const {
  334. const auto user = peer->asUser();
  335. const auto now = user ? base::unixtime::now() : TimeId();
  336. const auto channel = user ? nullptr : peer->asChannel();
  337. const auto nextLayer = [&] {
  338. if (hasUnreadBadgesAbove) {
  339. return kNoneLayer;
  340. } else if (user && Data::IsUserOnline(user, now)) {
  341. return kTopLayer;
  342. } else if (channel
  343. && (Data::ChannelHasActiveCall(channel)
  344. || Data::ChannelHasSubscriptionUntilDate(channel))) {
  345. return kTopLayer;
  346. } else if (peer->messagesTTL()) {
  347. return kBottomLayer;
  348. }
  349. return kNoneLayer;
  350. }();
  351. setCornerBadgeShown(nextLayer, std::move(updateCallback));
  352. if ((nextLayer == kTopLayer) && user) {
  353. peer->owner().watchForOffline(user, now);
  354. }
  355. }
  356. void Row::ensureCornerBadgeUserpic() const {
  357. if (_cornerBadgeUserpic) {
  358. return;
  359. }
  360. _cornerBadgeUserpic = std::make_unique<CornerBadgeUserpic>();
  361. }
  362. void Row::PaintCornerBadgeFrame(
  363. not_null<CornerBadgeUserpic*> data,
  364. int framePadding,
  365. not_null<Entry*> entry,
  366. PeerData *peer,
  367. Ui::VideoUserpic *videoUserpic,
  368. Ui::PeerUserpicView &view,
  369. const Ui::PaintContext &context,
  370. bool subscribed) {
  371. data->frame.fill(Qt::transparent);
  372. Painter q(&data->frame);
  373. q.translate(framePadding, framePadding);
  374. auto hq = std::optional<PainterHighQualityEnabler>();
  375. const auto photoSize = context.st->photoSize;
  376. const auto storiesCount = data->storiesCount;
  377. if (storiesCount) {
  378. hq.emplace(q);
  379. const auto line = st::dialogsStoriesFull.lineTwice / 2.;
  380. const auto skip = line * 3 / 2.;
  381. const auto scale = 1. - (2 * skip / photoSize);
  382. const auto center = photoSize / 2.;
  383. q.save();
  384. q.translate(center, center);
  385. q.scale(scale, scale);
  386. q.translate(-center, -center);
  387. }
  388. q.translate(-context.st->padding.left(), -context.st->padding.top());
  389. PaintUserpic(
  390. q,
  391. entry,
  392. peer,
  393. videoUserpic,
  394. view,
  395. context);
  396. q.translate(context.st->padding.left(), context.st->padding.top());
  397. if (storiesCount) {
  398. q.restore();
  399. const auto outline = QRectF(0, 0, photoSize, photoSize);
  400. const auto storiesUnreadCount = data->storiesUnreadCount;
  401. const auto storiesUnreadBrush = [&] {
  402. if (context.active || !storiesUnreadCount) {
  403. return st::dialogsUnreadBgMutedActive->b;
  404. }
  405. auto gradient = Ui::UnreadStoryOutlineGradient(outline);
  406. return QBrush(gradient);
  407. }();
  408. const auto storiesBrush = context.active
  409. ? st::dialogsUnreadBgMutedActive->b
  410. : st::dialogsUnreadBgMuted->b;
  411. const auto storiesUnread = st::dialogsStoriesFull.lineTwice / 2.;
  412. const auto storiesLine = st::dialogsStoriesFull.lineReadTwice / 2.;
  413. auto segments = std::vector<Ui::OutlineSegment>();
  414. segments.reserve(storiesCount);
  415. const auto storiesReadCount = storiesCount - storiesUnreadCount;
  416. for (auto i = 0; i != storiesReadCount; ++i) {
  417. segments.push_back({ storiesBrush, storiesLine });
  418. }
  419. for (auto i = 0; i != storiesUnreadCount; ++i) {
  420. segments.push_back({ storiesUnreadBrush, storiesUnread });
  421. }
  422. if (peer && peer->forum()) {
  423. const auto radius = context.st->photoSize
  424. * Ui::ForumUserpicRadiusMultiplier();
  425. Ui::PaintOutlineSegments(q, outline, radius, segments);
  426. } else {
  427. Ui::PaintOutlineSegments(q, outline, segments);
  428. }
  429. }
  430. if (subscribed) {
  431. if (!hq) {
  432. hq.emplace(q);
  433. }
  434. // TODO: Unnecessarily repaints on activating peer.
  435. q.setCompositionMode(QPainter::CompositionMode_Source);
  436. const auto &s = st::dialogsSubscriptionBadgeSkip;
  437. auto path = SubscriptionOutlinePath();
  438. const auto x = photoSize - s.x() - st::dialogsSubscriptionBadgeSize;
  439. const auto y = photoSize - s.y() - st::dialogsSubscriptionBadgeSize;
  440. q.translate(x, y);
  441. q.fillPath(path, Qt::transparent);
  442. q.setCompositionMode(QPainter::CompositionMode_SourceOver);
  443. q.resetTransform();
  444. q.drawImage(x, y, SubscriptionIcon());
  445. return;
  446. }
  447. const auto &manager = data->layersManager;
  448. if (const auto p = manager.progressForLayer(kBottomLayer); p > 0.) {
  449. const auto size = photoSize;
  450. if (data->cacheTTL.isNull() && peer && peer->messagesTTL()) {
  451. data->cacheTTL = CornerBadgeTTL(peer, view, size);
  452. }
  453. q.setOpacity(p);
  454. const auto point = CornerBadgeTTLRect(size).topLeft();
  455. q.drawImage(point, data->cacheTTL);
  456. q.setOpacity(1.);
  457. }
  458. const auto topLayerProgress = manager.progressForLayer(kTopLayer);
  459. if (!topLayerProgress) {
  460. return;
  461. }
  462. if (!hq) {
  463. hq.emplace(q);
  464. }
  465. q.setCompositionMode(QPainter::CompositionMode_Source);
  466. const auto online = peer && peer->isUser();
  467. const auto size = online
  468. ? st::dialogsOnlineBadgeSize
  469. : st::dialogsCallBadgeSize;
  470. const auto stroke = st::dialogsOnlineBadgeStroke;
  471. const auto skip = online
  472. ? st::dialogsOnlineBadgeSkip
  473. : st::dialogsCallBadgeSkip;
  474. const auto shrink = (size / 2) * (1. - topLayerProgress);
  475. auto pen = QPen(Qt::transparent);
  476. pen.setWidthF(stroke * topLayerProgress);
  477. q.setPen(pen);
  478. q.setBrush(data->active
  479. ? st::dialogsOnlineBadgeFgActive
  480. : st::dialogsOnlineBadgeFg);
  481. q.drawEllipse(QRectF(
  482. photoSize - skip.x() - size,
  483. photoSize - skip.y() - size,
  484. size,
  485. size
  486. ).marginsRemoved({ shrink, shrink, shrink, shrink }));
  487. }
  488. void Row::paintUserpic(
  489. Painter &p,
  490. not_null<Entry*> entry,
  491. PeerData *peer,
  492. Ui::VideoUserpic *videoUserpic,
  493. const Ui::PaintContext &context,
  494. bool hasUnreadBadgesAbove) const {
  495. if (peer) {
  496. updateCornerBadgeShown(peer, nullptr, hasUnreadBadgesAbove);
  497. }
  498. const auto cornerBadgeShown = !_cornerBadgeUserpic
  499. ? _cornerBadgeShown
  500. : !_cornerBadgeUserpic->layersManager.isDisplayedNone();
  501. const auto storiesPeer = peer
  502. ? ((peer->isUser() || peer->isChannel()) ? peer : nullptr)
  503. : nullptr;
  504. const auto storiesFolder = peer ? nullptr : _id.folder();
  505. const auto storiesHas = storiesPeer
  506. ? storiesPeer->hasActiveStories()
  507. : storiesFolder
  508. ? storiesFolder->storiesCount()
  509. : false;
  510. if (!cornerBadgeShown && !storiesHas) {
  511. BasicRow::paintUserpic(p, entry, peer, videoUserpic, context, false);
  512. if (!peer || !_cornerBadgeShown) {
  513. _cornerBadgeUserpic = nullptr;
  514. }
  515. return;
  516. }
  517. ensureCornerBadgeUserpic();
  518. const auto ratio = style::DevicePixelRatio();
  519. const auto framePadding = std::max({
  520. -st::dialogsCallBadgeSkip.x(),
  521. -st::dialogsCallBadgeSkip.y(),
  522. st::lineWidth * 2 });
  523. const auto frameSide = (2 * framePadding + context.st->photoSize)
  524. * ratio;
  525. const auto frameSize = QSize(frameSide, frameSide);
  526. const auto storiesSource = (storiesHas && storiesPeer)
  527. ? storiesPeer->owner().stories().source(storiesPeer->id)
  528. : nullptr;
  529. const auto storiesCountReal = storiesSource
  530. ? int(storiesSource->ids.size())
  531. : storiesFolder
  532. ? storiesFolder->storiesCount()
  533. : storiesHas
  534. ? 1
  535. : 0;
  536. const auto storiesUnreadCountReal = storiesSource
  537. ? storiesSource->unreadCount()
  538. : storiesFolder
  539. ? storiesFolder->storiesUnreadCount()
  540. : (storiesPeer && storiesPeer->hasUnreadStories())
  541. ? 1
  542. : 0;
  543. const auto limit = Ui::kOutlineSegmentsMax;
  544. const auto storiesCount = std::min(storiesCountReal, limit);
  545. const auto storiesUnreadCount = std::min(storiesUnreadCountReal, limit);
  546. if (_cornerBadgeUserpic->frame.size() != frameSize) {
  547. _cornerBadgeUserpic->frame = QImage(
  548. frameSize,
  549. QImage::Format_ARGB32_Premultiplied);
  550. _cornerBadgeUserpic->frame.setDevicePixelRatio(ratio);
  551. }
  552. auto key = peer ? peer->userpicUniqueKey(userpicView()) : InMemoryKey();
  553. key.first += peer ? peer->messagesTTL() : 0;
  554. const auto frameIndex = videoUserpic ? videoUserpic->frameIndex() : -1;
  555. const auto paletteVersionReal = style::PaletteVersion();
  556. const auto paletteVersion = (paletteVersionReal & ((1 << 17) - 1));
  557. const auto active = context.active ? 1 : 0;
  558. const auto keyChanged = (_cornerBadgeUserpic->key != key)
  559. || (_cornerBadgeUserpic->paletteVersion != paletteVersion);
  560. if (keyChanged) {
  561. _cornerBadgeUserpic->cacheTTL = QImage();
  562. }
  563. const auto subscribed = Data::ChannelHasSubscriptionUntilDate(
  564. peer ? peer->asChannel() : nullptr);
  565. if (keyChanged
  566. || !_cornerBadgeUserpic->layersManager.isFinished()
  567. || _cornerBadgeUserpic->active != active
  568. || _cornerBadgeUserpic->frameIndex != frameIndex
  569. || _cornerBadgeUserpic->storiesCount != storiesCount
  570. || _cornerBadgeUserpic->storiesUnreadCount != storiesUnreadCount
  571. || videoUserpic) {
  572. _cornerBadgeUserpic->key = key;
  573. _cornerBadgeUserpic->paletteVersion = paletteVersion;
  574. _cornerBadgeUserpic->active = active;
  575. _cornerBadgeUserpic->storiesCount = storiesCount;
  576. _cornerBadgeUserpic->storiesUnreadCount = storiesUnreadCount;
  577. _cornerBadgeUserpic->frameIndex = frameIndex;
  578. _cornerBadgeUserpic->layersManager.markFrameShown();
  579. PaintCornerBadgeFrame(
  580. _cornerBadgeUserpic.get(),
  581. framePadding,
  582. _id.entry(),
  583. peer,
  584. videoUserpic,
  585. userpicView(),
  586. context,
  587. subscribed);
  588. }
  589. p.drawImage(
  590. context.st->padding.left() - framePadding,
  591. context.st->padding.top() - framePadding,
  592. _cornerBadgeUserpic->frame);
  593. const auto history = _id.history();
  594. if (!history || history->peer->isUser() || subscribed) {
  595. return;
  596. }
  597. const auto actionPainter = history->sendActionPainter();
  598. const auto bg = context.active
  599. ? st::dialogsBgActive
  600. : st::dialogsBg;
  601. const auto size = st::dialogsCallBadgeSize;
  602. const auto skip = st::dialogsCallBadgeSkip;
  603. p.setOpacity(
  604. _cornerBadgeUserpic->layersManager.progressForLayer(kTopLayer));
  605. p.translate(context.st->padding.left(), context.st->padding.top());
  606. actionPainter->paintSpeaking(
  607. p,
  608. context.st->photoSize - skip.x() - size,
  609. context.st->photoSize - skip.y() - size,
  610. context.width,
  611. bg,
  612. context.now);
  613. p.translate(-context.st->padding.left(), -context.st->padding.top());
  614. p.setOpacity(1.);
  615. }
  616. bool Row::lookupIsInTopicJump(int x, int y) const {
  617. const auto history = this->history();
  618. return history && history->lastItemDialogsView().isInTopicJump(x, y);
  619. }
  620. void Row::stopLastRipple() {
  621. BasicRow::stopLastRipple();
  622. const auto history = this->history();
  623. const auto view = history ? &history->lastItemDialogsView() : nullptr;
  624. if (view) {
  625. view->stopLastRipple();
  626. }
  627. }
  628. void Row::clearRipple() {
  629. BasicRow::clearRipple();
  630. clearTopicJumpRipple();
  631. }
  632. void Row::addTopicJumpRipple(
  633. QPoint origin,
  634. not_null<Ui::TopicJumpCache*> topicJumpCache,
  635. Fn<void()> updateCallback) {
  636. const auto history = this->history();
  637. const auto view = history ? &history->lastItemDialogsView() : nullptr;
  638. if (view) {
  639. view->addTopicJumpRipple(
  640. origin,
  641. topicJumpCache,
  642. std::move(updateCallback));
  643. _topicJumpRipple = 1;
  644. }
  645. }
  646. void Row::clearTopicJumpRipple() {
  647. if (!_topicJumpRipple) {
  648. return;
  649. }
  650. const auto history = this->history();
  651. const auto view = history ? &history->lastItemDialogsView() : nullptr;
  652. if (view) {
  653. view->clearRipple();
  654. }
  655. _topicJumpRipple = 0;
  656. }
  657. bool Row::topicJumpRipple() const {
  658. return _topicJumpRipple != 0;
  659. }
  660. FakeRow::FakeRow(
  661. Key searchInChat,
  662. not_null<HistoryItem*> item,
  663. Fn<void()> repaint)
  664. : _searchInChat(searchInChat)
  665. , _item(item)
  666. , _repaint(std::move(repaint)) {
  667. invalidateTopic();
  668. }
  669. void FakeRow::invalidateTopic() {
  670. _topic = _item->topic();
  671. if (_topic) {
  672. return;
  673. } else if (const auto rootId = _item->topicRootId()) {
  674. if (const auto forum = _item->history()->asForum()) {
  675. if (!forum->topicDeleted(rootId)) {
  676. forum->requestTopic(rootId, crl::guard(this, [=] {
  677. _topic = _item->topic();
  678. if (_topic) {
  679. _repaint();
  680. }
  681. }));
  682. }
  683. }
  684. }
  685. }
  686. const Ui::Text::String &FakeRow::name() const {
  687. if (_name.isEmpty()) {
  688. const auto from = _searchInChat
  689. ? _item->displayFrom()
  690. : nullptr;
  691. const auto peer = from ? from : _item->history()->peer.get();
  692. _name.setText(
  693. st::semiboldTextStyle,
  694. peer->name(),
  695. Ui::NameTextOptions());
  696. }
  697. return _name;
  698. }
  699. } // namespace Dialogs