group_call_userpics.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  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/group_call_userpics.h"
  8. #include "ui/paint/blobs.h"
  9. #include "ui/painter.h"
  10. #include "ui/power_saving.h"
  11. #include "base/random.h"
  12. #include "styles/style_chat.h"
  13. #include "styles/style_chat_helpers.h"
  14. namespace Ui {
  15. namespace {
  16. constexpr auto kDuration = 160;
  17. constexpr auto kMaxUserpics = 4;
  18. constexpr auto kWideScale = 5;
  19. constexpr auto kBlobsEnterDuration = crl::time(250);
  20. constexpr auto kLevelDuration = 100. + 500. * 0.23;
  21. constexpr auto kBlobScale = 0.605;
  22. constexpr auto kMinorBlobFactor = 0.9f;
  23. constexpr auto kUserpicMinScale = 0.8;
  24. constexpr auto kMaxLevel = 1.;
  25. constexpr auto kSendRandomLevelInterval = crl::time(100);
  26. auto Blobs()->std::array<Ui::Paint::Blobs::BlobData, 2> {
  27. return { {
  28. {
  29. .segmentsCount = 6,
  30. .minScale = kBlobScale * kMinorBlobFactor,
  31. .minRadius = st::historyGroupCallBlobMinRadius * kMinorBlobFactor,
  32. .maxRadius = st::historyGroupCallBlobMaxRadius * kMinorBlobFactor,
  33. .speedScale = 1.,
  34. .alpha = .5,
  35. },
  36. {
  37. .segmentsCount = 8,
  38. .minScale = kBlobScale,
  39. .minRadius = (float)st::historyGroupCallBlobMinRadius,
  40. .maxRadius = (float)st::historyGroupCallBlobMaxRadius,
  41. .speedScale = 1.,
  42. .alpha = .2,
  43. },
  44. } };
  45. }
  46. } // namespace
  47. struct GroupCallUserpics::BlobsAnimation {
  48. BlobsAnimation(
  49. std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
  50. float levelDuration,
  51. float maxLevel)
  52. : blobs(std::move(blobDatas), levelDuration, maxLevel) {
  53. }
  54. Ui::Paint::Blobs blobs;
  55. crl::time lastTime = 0;
  56. crl::time lastSpeakingUpdateTime = 0;
  57. float64 enter = 0.;
  58. };
  59. struct GroupCallUserpics::Userpic {
  60. User data;
  61. std::pair<uint64, uint64> cacheKey;
  62. crl::time speakingStarted = 0;
  63. QImage cache;
  64. Animations::Simple leftAnimation;
  65. Animations::Simple shownAnimation;
  66. std::unique_ptr<BlobsAnimation> blobsAnimation;
  67. int left = 0;
  68. bool positionInited = false;
  69. bool topMost = false;
  70. bool hiding = false;
  71. bool cacheMasked = false;
  72. };
  73. GroupCallUserpics::GroupCallUserpics(
  74. const style::GroupCallUserpics &st,
  75. rpl::producer<bool> &&hideBlobs,
  76. Fn<void()> repaint)
  77. : _st(st)
  78. , _randomSpeakingTimer([=] { sendRandomLevels(); })
  79. , _repaint(std::move(repaint)) {
  80. const auto limit = kMaxUserpics;
  81. const auto single = _st.size;
  82. const auto shift = _st.shift;
  83. // + 1 * single for the blobs.
  84. _maxWidth = 2 * single + (limit - 1) * (single - shift);
  85. style::PaletteChanged(
  86. ) | rpl::start_with_next([=] {
  87. for (auto &userpic : _list) {
  88. userpic.cache = QImage();
  89. }
  90. }, lifetime());
  91. _speakingAnimation.init([=](crl::time now) {
  92. if (const auto &last = _speakingAnimationHideLastTime; (last > 0)
  93. && (now - last >= kBlobsEnterDuration)) {
  94. _speakingAnimation.stop();
  95. }
  96. for (auto &userpic : _list) {
  97. if (const auto blobs = userpic.blobsAnimation.get()) {
  98. blobs->blobs.updateLevel(now - blobs->lastTime);
  99. blobs->lastTime = now;
  100. }
  101. }
  102. if (const auto onstack = _repaint) {
  103. onstack();
  104. }
  105. });
  106. rpl::combine(
  107. PowerSaving::OnValue(PowerSaving::kCalls),
  108. std::move(hideBlobs)
  109. ) | rpl::start_with_next([=](bool disabled, bool deactivated) {
  110. const auto hide = disabled || deactivated;
  111. if (!(hide && _speakingAnimationHideLastTime)) {
  112. _speakingAnimationHideLastTime = hide ? crl::now() : 0;
  113. }
  114. _skipLevelUpdate = hide;
  115. for (auto &userpic : _list) {
  116. if (const auto blobs = userpic.blobsAnimation.get()) {
  117. blobs->blobs.setLevel(0.);
  118. }
  119. }
  120. if (!hide && !_speakingAnimation.animating()) {
  121. _speakingAnimation.start();
  122. }
  123. _skipLevelUpdate = hide;
  124. }, lifetime());
  125. }
  126. GroupCallUserpics::~GroupCallUserpics() = default;
  127. void GroupCallUserpics::paint(QPainter &p, int x, int y, int size) {
  128. const auto factor = style::DevicePixelRatio();
  129. const auto &minScale = kUserpicMinScale;
  130. for (auto &userpic : ranges::views::reverse(_list)) {
  131. const auto shown = userpic.shownAnimation.value(
  132. userpic.hiding ? 0. : 1.);
  133. if (shown == 0.) {
  134. continue;
  135. }
  136. validateCache(userpic);
  137. p.setOpacity(shown);
  138. const auto left = x + userpic.leftAnimation.value(userpic.left);
  139. const auto blobs = userpic.blobsAnimation.get();
  140. const auto shownScale = 0.5 + shown / 2.;
  141. const auto scale = shownScale * (!blobs
  142. ? 1.
  143. : (minScale
  144. + (1. - minScale) * (_speakingAnimationHideLastTime
  145. ? (1. - blobs->blobs.currentLevel())
  146. : blobs->blobs.currentLevel())));
  147. if (blobs) {
  148. auto hq = PainterHighQualityEnabler(p);
  149. const auto shift = QPointF(left + size / 2., y + size / 2.);
  150. p.translate(shift);
  151. blobs->blobs.paint(p, st::windowActiveTextFg);
  152. p.translate(-shift);
  153. p.setOpacity(1.);
  154. }
  155. if (std::abs(scale - 1.) < 0.001) {
  156. const auto skip = ((kWideScale - 1) / 2) * size * factor;
  157. p.drawImage(
  158. QRect(left, y, size, size),
  159. userpic.cache,
  160. QRect(skip, skip, size * factor, size * factor));
  161. } else {
  162. auto hq = PainterHighQualityEnabler(p);
  163. auto target = QRect(
  164. left + (1 - kWideScale) / 2 * size,
  165. y + (1 - kWideScale) / 2 * size,
  166. kWideScale * size,
  167. kWideScale * size);
  168. auto shrink = anim::interpolate(
  169. (1 - kWideScale) / 2 * size,
  170. 0,
  171. scale);
  172. auto margins = QMargins(shrink, shrink, shrink, shrink);
  173. p.drawImage(target.marginsAdded(margins), userpic.cache);
  174. }
  175. }
  176. p.setOpacity(1.);
  177. const auto hidden = [](const Userpic &userpic) {
  178. return userpic.hiding && !userpic.shownAnimation.animating();
  179. };
  180. _list.erase(ranges::remove_if(_list, hidden), end(_list));
  181. }
  182. int GroupCallUserpics::maxWidth() const {
  183. return _maxWidth;
  184. }
  185. rpl::producer<int> GroupCallUserpics::widthValue() const {
  186. return _width.value();
  187. }
  188. bool GroupCallUserpics::needCacheRefresh(Userpic &userpic) {
  189. if (userpic.cache.isNull()) {
  190. return true;
  191. } else if (userpic.hiding) {
  192. return false;
  193. } else if (userpic.cacheKey != userpic.data.userpicKey) {
  194. return true;
  195. }
  196. const auto shouldBeMasked = !userpic.topMost;
  197. if (userpic.cacheMasked == shouldBeMasked || !shouldBeMasked) {
  198. return true;
  199. }
  200. return !userpic.leftAnimation.animating();
  201. }
  202. void GroupCallUserpics::ensureBlobsAnimation(Userpic &userpic) {
  203. if (userpic.blobsAnimation) {
  204. return;
  205. }
  206. userpic.blobsAnimation = std::make_unique<BlobsAnimation>(
  207. Blobs() | ranges::to_vector,
  208. kLevelDuration,
  209. kMaxLevel);
  210. userpic.blobsAnimation->lastTime = crl::now();
  211. }
  212. void GroupCallUserpics::sendRandomLevels() {
  213. if (_skipLevelUpdate) {
  214. return;
  215. }
  216. for (auto &userpic : _list) {
  217. if (const auto blobs = userpic.blobsAnimation.get()) {
  218. const auto value = 30 + base::RandomIndex(70);
  219. userpic.blobsAnimation->blobs.setLevel(float64(value) / 100.);
  220. }
  221. }
  222. }
  223. void GroupCallUserpics::validateCache(Userpic &userpic) {
  224. if (!needCacheRefresh(userpic)) {
  225. return;
  226. }
  227. const auto factor = style::DevicePixelRatio();
  228. const auto size = _st.size;
  229. const auto shift = _st.shift;
  230. const auto full = QSize(size, size) * kWideScale * factor;
  231. if (userpic.cache.isNull()) {
  232. userpic.cache = QImage(full, QImage::Format_ARGB32_Premultiplied);
  233. userpic.cache.setDevicePixelRatio(factor);
  234. }
  235. userpic.cacheKey = userpic.data.userpicKey;
  236. userpic.cacheMasked = !userpic.topMost;
  237. userpic.cache.fill(Qt::transparent);
  238. {
  239. auto p = QPainter(&userpic.cache);
  240. const auto skip = (kWideScale - 1) / 2 * size;
  241. p.drawImage(QRect(skip, skip, size, size), userpic.data.userpic);
  242. if (userpic.cacheMasked) {
  243. auto hq = PainterHighQualityEnabler(p);
  244. auto pen = QPen(Qt::transparent);
  245. pen.setWidth(_st.stroke);
  246. p.setCompositionMode(QPainter::CompositionMode_Source);
  247. p.setBrush(Qt::transparent);
  248. p.setPen(pen);
  249. p.drawEllipse(skip - size + shift, skip, size, size);
  250. }
  251. }
  252. }
  253. void GroupCallUserpics::update(
  254. const std::vector<GroupCallUser> &users,
  255. bool visible) {
  256. const auto idFromUserpic = [](const Userpic &userpic) {
  257. return userpic.data.id;
  258. };
  259. // Use "topMost" as "willBeHidden" flag.
  260. for (auto &userpic : _list) {
  261. userpic.topMost = true;
  262. }
  263. for (const auto &user : users) {
  264. const auto i = ranges::find(_list, user.id, idFromUserpic);
  265. if (i == end(_list)) {
  266. _list.push_back(Userpic{ user });
  267. toggle(_list.back(), true);
  268. continue;
  269. }
  270. i->topMost = false;
  271. if (i->hiding) {
  272. toggle(*i, true);
  273. }
  274. i->data = user;
  275. // Put this one after the last we are not hiding.
  276. for (auto j = end(_list) - 1; j != i; --j) {
  277. if (!j->topMost) {
  278. ranges::rotate(i, i + 1, j + 1);
  279. break;
  280. }
  281. }
  282. }
  283. // Hide the ones that "willBeHidden" (currently having "topMost" flag).
  284. // Set correct real values of "topMost" flag.
  285. const auto userpicsBegin = begin(_list);
  286. const auto userpicsEnd = end(_list);
  287. auto markedTopMost = userpicsEnd;
  288. auto hasBlobs = false;
  289. for (auto i = userpicsBegin; i != userpicsEnd; ++i) {
  290. auto &userpic = *i;
  291. if (userpic.data.speaking) {
  292. ensureBlobsAnimation(userpic);
  293. hasBlobs = true;
  294. } else {
  295. userpic.blobsAnimation = nullptr;
  296. }
  297. if (userpic.topMost) {
  298. toggle(userpic, false);
  299. userpic.topMost = false;
  300. } else if (markedTopMost == userpicsEnd) {
  301. userpic.topMost = true;
  302. markedTopMost = i;
  303. }
  304. }
  305. if (markedTopMost != userpicsEnd && markedTopMost != userpicsBegin) {
  306. // Bring the topMost userpic to the very beginning, above all hiding.
  307. std::rotate(userpicsBegin, markedTopMost, markedTopMost + 1);
  308. }
  309. updatePositions();
  310. if (!hasBlobs) {
  311. _randomSpeakingTimer.cancel();
  312. _speakingAnimation.stop();
  313. } else if (!_randomSpeakingTimer.isActive()) {
  314. _randomSpeakingTimer.callEach(kSendRandomLevelInterval);
  315. _speakingAnimation.start();
  316. }
  317. if (visible) {
  318. recountAndRepaint();
  319. } else {
  320. finishAnimating();
  321. }
  322. }
  323. void GroupCallUserpics::finishAnimating() {
  324. for (auto &userpic : _list) {
  325. userpic.shownAnimation.stop();
  326. userpic.leftAnimation.stop();
  327. }
  328. recountAndRepaint();
  329. }
  330. void GroupCallUserpics::toggle(Userpic &userpic, bool shown) {
  331. if (userpic.hiding == !shown && !userpic.shownAnimation.animating()) {
  332. return;
  333. }
  334. userpic.hiding = !shown;
  335. userpic.shownAnimation.start(
  336. [=] { recountAndRepaint(); },
  337. shown ? 0. : 1.,
  338. shown ? 1. : 0.,
  339. kDuration);
  340. }
  341. void GroupCallUserpics::updatePositions() {
  342. const auto shownCount = ranges::count(_list, false, &Userpic::hiding);
  343. if (!shownCount) {
  344. return;
  345. }
  346. const auto single = _st.size;
  347. const auto shift = _st.shift;
  348. // + 1 * single for the blobs.
  349. const auto fullWidth = single + (shownCount - 1) * (single - shift);
  350. auto left = (_st.align & Qt::AlignLeft)
  351. ? 0
  352. : (_st.align & Qt::AlignHCenter)
  353. ? (-fullWidth / 2)
  354. : -fullWidth;
  355. for (auto &userpic : _list) {
  356. if (userpic.hiding) {
  357. continue;
  358. }
  359. if (!userpic.positionInited) {
  360. userpic.positionInited = true;
  361. userpic.left = left;
  362. } else if (userpic.left != left) {
  363. userpic.leftAnimation.start(
  364. _repaint,
  365. userpic.left,
  366. left,
  367. kDuration);
  368. userpic.left = left;
  369. }
  370. left += (single - shift);
  371. }
  372. }
  373. void GroupCallUserpics::recountAndRepaint() {
  374. auto width = 0;
  375. auto maxShown = 0.;
  376. for (const auto &userpic : _list) {
  377. const auto shown = userpic.shownAnimation.value(
  378. userpic.hiding ? 0. : 1.);
  379. if (shown > maxShown) {
  380. maxShown = shown;
  381. }
  382. width += anim::interpolate(0, _st.size - _st.shift, shown);
  383. }
  384. _width = width + anim::interpolate(0, _st.shift, maxShown);
  385. if (_repaint) {
  386. _repaint();
  387. }
  388. }
  389. } // namespace Ui