calls_group_members_row.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  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 "calls/group/calls_group_members_row.h"
  8. #include "calls/group/calls_group_call.h"
  9. #include "calls/group/calls_group_common.h"
  10. #include "data/data_peer.h"
  11. #include "data/data_group_call.h"
  12. #include "ui/paint/arcs.h"
  13. #include "ui/paint/blobs.h"
  14. #include "ui/text/text_options.h"
  15. #include "ui/effects/ripple_animation.h"
  16. #include "ui/painter.h"
  17. #include "lang/lang_keys.h"
  18. #include "webrtc/webrtc_video_track.h"
  19. #include "styles/style_calls.h"
  20. namespace Calls::Group {
  21. namespace {
  22. constexpr auto kLevelDuration = 100. + 500. * 0.23;
  23. constexpr auto kBlobScale = 0.605;
  24. constexpr auto kMinorBlobFactor = 0.9f;
  25. constexpr auto kUserpicMinScale = 0.8;
  26. constexpr auto kMaxLevel = 1.;
  27. constexpr auto kWideScale = 5;
  28. constexpr auto kArcsStrokeRatio = 0.8;
  29. const auto kSpeakerThreshold = std::vector<float>{
  30. Group::kDefaultVolume * 0.1f / Group::kMaxVolume,
  31. Group::kDefaultVolume * 0.9f / Group::kMaxVolume };
  32. auto RowBlobs() -> std::array<Ui::Paint::Blobs::BlobData, 2> {
  33. return { {
  34. {
  35. .segmentsCount = 6,
  36. .minScale = kBlobScale * kMinorBlobFactor,
  37. .minRadius = st::groupCallRowBlobMinRadius * kMinorBlobFactor,
  38. .maxRadius = st::groupCallRowBlobMaxRadius * kMinorBlobFactor,
  39. .speedScale = 1.,
  40. .alpha = .5,
  41. },
  42. {
  43. .segmentsCount = 8,
  44. .minScale = kBlobScale,
  45. .minRadius = (float)st::groupCallRowBlobMinRadius,
  46. .maxRadius = (float)st::groupCallRowBlobMaxRadius,
  47. .speedScale = 1.,
  48. .alpha = .2,
  49. },
  50. } };
  51. }
  52. [[nodiscard]] QString StatusPercentString(float volume) {
  53. return QString::number(int(base::SafeRound(volume * 200))) + '%';
  54. }
  55. [[nodiscard]] int StatusPercentWidth(const QString &percent) {
  56. return st::normalFont->width(percent);
  57. }
  58. } // namespace
  59. struct MembersRow::BlobsAnimation {
  60. BlobsAnimation(
  61. std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
  62. float levelDuration,
  63. float maxLevel);
  64. Ui::Paint::Blobs blobs;
  65. crl::time lastTime = 0;
  66. crl::time lastSoundingUpdateTime = 0;
  67. float64 enter = 0.;
  68. QImage userpicCache;
  69. InMemoryKey userpicKey;
  70. rpl::lifetime lifetime;
  71. };
  72. struct MembersRow::StatusIcon {
  73. StatusIcon(bool shown, float volume);
  74. const style::icon &speaker;
  75. Ui::Paint::ArcsAnimation arcs;
  76. Ui::Animations::Simple arcsAnimation;
  77. Ui::Animations::Simple shownAnimation;
  78. QString percent;
  79. int percentWidth = 0;
  80. int arcsWidth = 0;
  81. int wasArcsWidth = 0;
  82. bool shown = true;
  83. rpl::lifetime lifetime;
  84. };
  85. MembersRow::BlobsAnimation::BlobsAnimation(
  86. std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
  87. float levelDuration,
  88. float maxLevel)
  89. : blobs(std::move(blobDatas), levelDuration, maxLevel) {
  90. style::PaletteChanged(
  91. ) | rpl::start_with_next([=] {
  92. userpicCache = QImage();
  93. }, lifetime);
  94. }
  95. MembersRow::StatusIcon::StatusIcon(bool shown, float volume)
  96. : speaker(st::groupCallStatusSpeakerIcon)
  97. , arcs(
  98. st::groupCallStatusSpeakerArcsAnimation,
  99. kSpeakerThreshold,
  100. volume,
  101. Ui::Paint::ArcsAnimation::Direction::Right)
  102. , percent(StatusPercentString(volume))
  103. , percentWidth(StatusPercentWidth(percent))
  104. , shown(shown) {
  105. }
  106. MembersRow::MembersRow(
  107. not_null<MembersRowDelegate*> delegate,
  108. not_null<PeerData*> participantPeer)
  109. : PeerListRow(participantPeer)
  110. , _delegate(delegate) {
  111. refreshStatus();
  112. _about.setText(st::defaultTextStyle, participantPeer->about());
  113. }
  114. MembersRow::~MembersRow() = default;
  115. void MembersRow::setSkipLevelUpdate(bool value) {
  116. _skipLevelUpdate = value;
  117. }
  118. void MembersRow::updateState(
  119. const Data::GroupCallParticipant *participant) {
  120. setVolume(participant
  121. ? participant->volume
  122. : Group::kDefaultVolume);
  123. if (!participant) {
  124. setState(State::Invited);
  125. setSounding(false);
  126. setSpeaking(false);
  127. _mutedByMe = false;
  128. _raisedHandRating = 0;
  129. } else if (!participant->muted
  130. || (participant->sounding && participant->ssrc != 0)
  131. || (participant->additionalSounding
  132. && GetAdditionalAudioSsrc(participant->videoParams) != 0)) {
  133. setState(State::Active);
  134. setSounding((participant->sounding && participant->ssrc != 0)
  135. || (participant->additionalSounding
  136. && GetAdditionalAudioSsrc(participant->videoParams) != 0));
  137. setSpeaking((participant->speaking && participant->ssrc != 0)
  138. || (participant->additionalSpeaking
  139. && GetAdditionalAudioSsrc(participant->videoParams) != 0));
  140. _mutedByMe = participant->mutedByMe;
  141. _raisedHandRating = 0;
  142. } else if (participant->canSelfUnmute) {
  143. setState(State::Inactive);
  144. setSounding(false);
  145. setSpeaking(false);
  146. _mutedByMe = participant->mutedByMe;
  147. _raisedHandRating = 0;
  148. } else {
  149. setSounding(false);
  150. setSpeaking(false);
  151. _mutedByMe = participant->mutedByMe;
  152. _raisedHandRating = participant->raisedHandRating;
  153. setState(_raisedHandRating ? State::RaisedHand : State::Muted);
  154. }
  155. refreshStatus();
  156. }
  157. void MembersRow::setSpeaking(bool speaking) {
  158. if (_speaking == speaking) {
  159. return;
  160. }
  161. _speaking = speaking;
  162. _speakingAnimation.start(
  163. [=] { _delegate->rowUpdateRow(this); },
  164. _speaking ? 0. : 1.,
  165. _speaking ? 1. : 0.,
  166. st::widgetFadeDuration);
  167. if (!_speaking
  168. || _mutedByMe
  169. || (_state == State::Muted)
  170. || (_state == State::RaisedHand)) {
  171. if (_statusIcon) {
  172. _statusIcon = nullptr;
  173. _delegate->rowUpdateRow(this);
  174. }
  175. } else if (!_statusIcon) {
  176. _statusIcon = std::make_unique<StatusIcon>(
  177. (_volume != Group::kDefaultVolume),
  178. (float)_volume / Group::kMaxVolume);
  179. _statusIcon->arcs.setStrokeRatio(kArcsStrokeRatio);
  180. _statusIcon->arcsWidth = _statusIcon->arcs.finishedWidth();
  181. _statusIcon->arcs.startUpdateRequests(
  182. ) | rpl::start_with_next([=] {
  183. if (!_statusIcon->arcsAnimation.animating()) {
  184. _statusIcon->wasArcsWidth = _statusIcon->arcsWidth;
  185. }
  186. auto callback = [=](float64 value) {
  187. _statusIcon->arcs.update(crl::now());
  188. _statusIcon->arcsWidth = anim::interpolate(
  189. _statusIcon->wasArcsWidth,
  190. _statusIcon->arcs.finishedWidth(),
  191. value);
  192. _delegate->rowUpdateRow(this);
  193. };
  194. _statusIcon->arcsAnimation.start(
  195. std::move(callback),
  196. 0.,
  197. 1.,
  198. st::groupCallSpeakerArcsAnimation.duration);
  199. }, _statusIcon->lifetime);
  200. }
  201. }
  202. void MembersRow::setSounding(bool sounding) {
  203. if (_sounding == sounding) {
  204. return;
  205. }
  206. _sounding = sounding;
  207. if (!_sounding) {
  208. _blobsAnimation = nullptr;
  209. } else if (!_blobsAnimation) {
  210. _blobsAnimation = std::make_unique<BlobsAnimation>(
  211. RowBlobs() | ranges::to_vector,
  212. kLevelDuration,
  213. kMaxLevel);
  214. _blobsAnimation->lastTime = crl::now();
  215. updateLevel(GroupCall::kSpeakLevelThreshold);
  216. }
  217. }
  218. void MembersRow::clearRaisedHandStatus() {
  219. if (!_raisedHandStatus) {
  220. return;
  221. }
  222. _raisedHandStatus = false;
  223. refreshStatus();
  224. _delegate->rowUpdateRow(this);
  225. }
  226. void MembersRow::setState(State state) {
  227. if (_state == state) {
  228. return;
  229. }
  230. const auto wasActive = (_state == State::Active);
  231. const auto wasMuted = (_state == State::Muted)
  232. || (_state == State::RaisedHand);
  233. const auto wasRaisedHand = (_state == State::RaisedHand);
  234. _state = state;
  235. const auto nowActive = (_state == State::Active);
  236. const auto nowMuted = (_state == State::Muted)
  237. || (_state == State::RaisedHand);
  238. const auto nowRaisedHand = (_state == State::RaisedHand);
  239. if (!wasRaisedHand && nowRaisedHand) {
  240. _raisedHandStatus = true;
  241. _delegate->rowScheduleRaisedHandStatusRemove(this);
  242. }
  243. if (nowActive != wasActive) {
  244. _activeAnimation.start(
  245. [=] { _delegate->rowUpdateRow(this); },
  246. nowActive ? 0. : 1.,
  247. nowActive ? 1. : 0.,
  248. st::widgetFadeDuration);
  249. }
  250. if (nowMuted != wasMuted) {
  251. _mutedAnimation.start(
  252. [=] { _delegate->rowUpdateRow(this); },
  253. nowMuted ? 0. : 1.,
  254. nowMuted ? 1. : 0.,
  255. st::widgetFadeDuration);
  256. }
  257. }
  258. void MembersRow::setVolume(int volume) {
  259. _volume = volume;
  260. if (_statusIcon) {
  261. const auto floatVolume = (float)volume / Group::kMaxVolume;
  262. _statusIcon->arcs.setValue(floatVolume);
  263. _statusIcon->percent = StatusPercentString(floatVolume);
  264. _statusIcon->percentWidth = StatusPercentWidth(_statusIcon->percent);
  265. const auto shown = (volume != Group::kDefaultVolume);
  266. if (_statusIcon->shown != shown) {
  267. _statusIcon->shown = shown;
  268. _statusIcon->shownAnimation.start(
  269. [=] { _delegate->rowUpdateRow(this); },
  270. shown ? 0. : 1.,
  271. shown ? 1. : 0.,
  272. st::groupCallSpeakerArcsAnimation.duration);
  273. }
  274. }
  275. }
  276. void MembersRow::updateLevel(float level) {
  277. Expects(_blobsAnimation != nullptr);
  278. const auto spoke = (level >= GroupCall::kSpeakLevelThreshold)
  279. ? crl::now()
  280. : crl::time();
  281. if (spoke && _speaking) {
  282. _speakingLastTime = spoke;
  283. }
  284. if (_skipLevelUpdate) {
  285. return;
  286. }
  287. if (spoke) {
  288. _blobsAnimation->lastSoundingUpdateTime = spoke;
  289. }
  290. _blobsAnimation->blobs.setLevel(level);
  291. }
  292. void MembersRow::updateBlobAnimation(crl::time now) {
  293. Expects(_blobsAnimation != nullptr);
  294. const auto soundingFinishesAt = _blobsAnimation->lastSoundingUpdateTime
  295. + Data::GroupCall::kSoundStatusKeptFor;
  296. const auto soundingStartsFinishing = soundingFinishesAt
  297. - kBlobsEnterDuration;
  298. const auto soundingFinishes = (soundingStartsFinishing < now);
  299. if (soundingFinishes) {
  300. _blobsAnimation->enter = std::clamp(
  301. (soundingFinishesAt - now) / float64(kBlobsEnterDuration),
  302. 0.,
  303. 1.);
  304. } else if (_blobsAnimation->enter < 1.) {
  305. _blobsAnimation->enter = std::clamp(
  306. (_blobsAnimation->enter
  307. + ((now - _blobsAnimation->lastTime)
  308. / float64(kBlobsEnterDuration))),
  309. 0.,
  310. 1.);
  311. }
  312. _blobsAnimation->blobs.updateLevel(now - _blobsAnimation->lastTime);
  313. _blobsAnimation->lastTime = now;
  314. }
  315. void MembersRow::ensureUserpicCache(
  316. Ui::PeerUserpicView &view,
  317. int size) {
  318. Expects(_blobsAnimation != nullptr);
  319. const auto user = peer();
  320. const auto key = user->userpicUniqueKey(view);
  321. const auto full = QSize(size, size)
  322. * kWideScale
  323. * style::DevicePixelRatio();
  324. auto &cache = _blobsAnimation->userpicCache;
  325. if (cache.isNull()) {
  326. cache = QImage(full, QImage::Format_ARGB32_Premultiplied);
  327. cache.setDevicePixelRatio(style::DevicePixelRatio());
  328. } else if (_blobsAnimation->userpicKey == key
  329. && cache.size() == full) {
  330. return;
  331. }
  332. _blobsAnimation->userpicKey = key;
  333. cache.fill(Qt::transparent);
  334. {
  335. Painter p(&cache);
  336. const auto skip = (kWideScale - 1) / 2 * size;
  337. user->paintUserpicLeft(p, view, skip, skip, kWideScale * size, size);
  338. }
  339. }
  340. void MembersRow::paintBlobs(
  341. Painter &p,
  342. int x,
  343. int y,
  344. int sizew,
  345. int sizeh,
  346. PanelMode mode) {
  347. if (!_blobsAnimation) {
  348. return;
  349. }
  350. auto size = sizew;
  351. const auto shift = QPointF(x + size / 2., y + size / 2.);
  352. auto hq = PainterHighQualityEnabler(p);
  353. p.translate(shift);
  354. const auto brush = _mutedByMe
  355. ? st::groupCallMemberMutedIcon->b
  356. : anim::brush(
  357. st::groupCallMemberInactiveStatus,
  358. st::groupCallMemberActiveStatus,
  359. _speakingAnimation.value(_speaking ? 1. : 0.));
  360. _blobsAnimation->blobs.paint(p, brush);
  361. p.translate(-shift);
  362. p.setOpacity(1.);
  363. }
  364. void MembersRow::paintScaledUserpic(
  365. Painter &p,
  366. Ui::PeerUserpicView &userpic,
  367. int x,
  368. int y,
  369. int outerWidth,
  370. int sizew,
  371. int sizeh,
  372. PanelMode mode) {
  373. auto size = sizew;
  374. if (!_blobsAnimation) {
  375. peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
  376. return;
  377. }
  378. const auto enter = _blobsAnimation->enter;
  379. const auto &minScale = kUserpicMinScale;
  380. const auto scaleUserpic = minScale
  381. + (1. - minScale) * _blobsAnimation->blobs.currentLevel();
  382. const auto scale = scaleUserpic * enter + 1. * (1. - enter);
  383. if (scale == 1.) {
  384. peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
  385. return;
  386. }
  387. ensureUserpicCache(userpic, size);
  388. PainterHighQualityEnabler hq(p);
  389. auto target = QRect(
  390. x + (1 - kWideScale) / 2 * size,
  391. y + (1 - kWideScale) / 2 * size,
  392. kWideScale * size,
  393. kWideScale * size);
  394. auto shrink = anim::interpolate(
  395. (1 - kWideScale) / 2 * size,
  396. 0,
  397. scale);
  398. auto margins = QMargins(shrink, shrink, shrink, shrink);
  399. p.drawImage(
  400. target.marginsAdded(margins),
  401. _blobsAnimation->userpicCache);
  402. }
  403. void MembersRow::paintMuteIcon(
  404. QPainter &p,
  405. QRect iconRect,
  406. MembersRowStyle style) {
  407. _delegate->rowPaintIcon(p, iconRect, computeIconState(style));
  408. }
  409. auto MembersRow::generatePaintUserpicCallback(bool forceRound)
  410. -> PaintRoundImageCallback {
  411. return [=](Painter &p, int x, int y, int outerWidth, int size) {
  412. const auto outer = outerWidth;
  413. paintComplexUserpic(p, x, y, outer, size, size, PanelMode::Default);
  414. };
  415. }
  416. void MembersRow::paintComplexUserpic(
  417. Painter &p,
  418. int x,
  419. int y,
  420. int outerWidth,
  421. int sizew,
  422. int sizeh,
  423. PanelMode mode,
  424. bool selected) {
  425. paintBlobs(p, x, y, sizew, sizeh, mode);
  426. paintScaledUserpic(
  427. p,
  428. ensureUserpicView(),
  429. x,
  430. y,
  431. outerWidth,
  432. sizew,
  433. sizeh,
  434. mode);
  435. }
  436. int MembersRow::statusIconWidth(bool skipIcon) const {
  437. if (!_statusIcon || !_speaking) {
  438. return 0;
  439. }
  440. const auto shown = _statusIcon->shownAnimation.value(
  441. _statusIcon->shown ? 1. : 0.);
  442. const auto iconWidth = skipIcon
  443. ? 0
  444. : (_statusIcon->speaker.width() + _statusIcon->arcsWidth);
  445. const auto full = iconWidth
  446. + _statusIcon->percentWidth
  447. + st::normalFont->spacew;
  448. return int(base::SafeRound(shown * full));
  449. }
  450. int MembersRow::statusIconHeight() const {
  451. return (_statusIcon && _speaking) ? _statusIcon->speaker.height() : 0;
  452. }
  453. void MembersRow::paintStatusIcon(
  454. Painter &p,
  455. int x,
  456. int y,
  457. const style::PeerListItem &st,
  458. const style::font &font,
  459. bool selected,
  460. bool skipIcon) {
  461. if (!_statusIcon) {
  462. return;
  463. }
  464. const auto shown = _statusIcon->shownAnimation.value(
  465. _statusIcon->shown ? 1. : 0.);
  466. if (shown == 0.) {
  467. return;
  468. }
  469. p.setFont(font);
  470. const auto color = (_speaking
  471. ? st.statusFgActive
  472. : (selected ? st.statusFgOver : st.statusFg))->c;
  473. p.setPen(color);
  474. const auto speakerRect = QRect(
  475. QPoint(x, y + (font->height - statusIconHeight()) / 2),
  476. _statusIcon->speaker.size());
  477. const auto arcPosition = speakerRect.topLeft()
  478. + QPoint(
  479. speakerRect.width() - st::groupCallStatusSpeakerArcsSkip,
  480. speakerRect.height() / 2);
  481. const auto iconWidth = skipIcon
  482. ? 0
  483. : (speakerRect.width() + _statusIcon->arcsWidth);
  484. const auto fullWidth = iconWidth
  485. + _statusIcon->percentWidth
  486. + st::normalFont->spacew;
  487. p.save();
  488. if (shown < 1.) {
  489. const auto centerx = speakerRect.x() + fullWidth / 2;
  490. const auto centery = speakerRect.y() + speakerRect.height() / 2;
  491. p.translate(centerx, centery);
  492. p.scale(shown, shown);
  493. p.translate(-centerx, -centery);
  494. }
  495. if (!skipIcon) {
  496. _statusIcon->speaker.paint(
  497. p,
  498. speakerRect.topLeft(),
  499. speakerRect.width(),
  500. color);
  501. p.translate(arcPosition);
  502. _statusIcon->arcs.paint(p, color);
  503. p.translate(-arcPosition);
  504. }
  505. p.setFont(st::normalFont);
  506. p.setPen(st.statusFgActive);
  507. p.drawTextLeft(
  508. x + iconWidth,
  509. y,
  510. fullWidth,
  511. _statusIcon->percent);
  512. p.restore();
  513. }
  514. void MembersRow::setAbout(const QString &about) {
  515. if (_about.toString() == about) {
  516. return;
  517. }
  518. _about.setText(st::defaultTextStyle, about);
  519. _delegate->rowUpdateRow(this);
  520. }
  521. void MembersRow::paintStatusText(
  522. Painter &p,
  523. const style::PeerListItem &st,
  524. int x,
  525. int y,
  526. int availableWidth,
  527. int outerWidth,
  528. bool selected) {
  529. paintComplexStatusText(
  530. p,
  531. st,
  532. x,
  533. y,
  534. availableWidth,
  535. outerWidth,
  536. selected,
  537. MembersRowStyle::Default);
  538. }
  539. void MembersRow::paintComplexStatusText(
  540. Painter &p,
  541. const style::PeerListItem &st,
  542. int x,
  543. int y,
  544. int availableWidth,
  545. int outerWidth,
  546. bool selected,
  547. MembersRowStyle style) {
  548. const auto skip = (style == MembersRowStyle::Default)
  549. ? _delegate->rowPaintStatusIcon(
  550. p,
  551. x,
  552. y,
  553. outerWidth,
  554. this,
  555. computeIconState(MembersRowStyle::Narrow))
  556. : 0;
  557. const auto narrowMode = (skip > 0);
  558. x += skip;
  559. availableWidth -= skip;
  560. const auto &font = st::normalFont;
  561. const auto useAbout = !_about.isEmpty()
  562. && (style != MembersRowStyle::Video)
  563. && ((_state == State::RaisedHand && !_raisedHandStatus)
  564. || (_state != State::RaisedHand && !_speaking));
  565. if (!useAbout
  566. && _state != State::Invited
  567. && !_mutedByMe) {
  568. paintStatusIcon(p, x, y, st, font, selected, narrowMode);
  569. const auto translatedWidth = statusIconWidth(narrowMode);
  570. p.translate(translatedWidth, 0);
  571. const auto guard = gsl::finally([&] {
  572. p.translate(-translatedWidth, 0);
  573. });
  574. const auto &style = (!narrowMode
  575. || (_state == State::RaisedHand && _raisedHandStatus))
  576. ? st
  577. : st::groupCallNarrowMembersListItem;
  578. PeerListRow::paintStatusText(
  579. p,
  580. style,
  581. x,
  582. y,
  583. availableWidth - translatedWidth,
  584. outerWidth,
  585. selected);
  586. return;
  587. }
  588. p.setPen((style == MembersRowStyle::Video)
  589. ? st::groupCallVideoSubTextFg
  590. : _mutedByMe
  591. ? st::groupCallMemberMutedIcon
  592. : st::groupCallMemberNotJoinedStatus);
  593. if (!_mutedByMe && useAbout) {
  594. return _about.draw(p, {
  595. .position = QPoint(x, y),
  596. .outerWidth = outerWidth,
  597. .availableWidth = availableWidth,
  598. .elisionLines = 1,
  599. });
  600. } else {
  601. p.setFont(font);
  602. p.drawTextLeft(
  603. x,
  604. y,
  605. outerWidth,
  606. (_mutedByMe
  607. ? tr::lng_group_call_muted_by_me_status(tr::now)
  608. : _delegate->rowIsMe(peer())
  609. ? tr::lng_status_connecting(tr::now)
  610. : tr::lng_group_call_invited_status(tr::now)));
  611. }
  612. }
  613. QSize MembersRow::rightActionSize() const {
  614. return _delegate->rowIsNarrow() ? QSize() : QSize(
  615. st::groupCallActiveButton.width,
  616. st::groupCallActiveButton.height);
  617. }
  618. bool MembersRow::rightActionDisabled() const {
  619. return _delegate->rowIsMe(peer())
  620. || (_state == State::Invited)
  621. || !_delegate->rowCanMuteMembers();
  622. }
  623. QMargins MembersRow::rightActionMargins() const {
  624. return QMargins(
  625. 0,
  626. 0,
  627. st::groupCallMemberButtonSkip,
  628. 0);
  629. }
  630. void MembersRow::rightActionPaint(
  631. Painter &p,
  632. int x,
  633. int y,
  634. int outerWidth,
  635. bool selected,
  636. bool actionSelected) {
  637. auto size = rightActionSize();
  638. const auto iconRect = style::rtlrect(
  639. x,
  640. y,
  641. size.width(),
  642. size.height(),
  643. outerWidth);
  644. if (_state == State::Invited) {
  645. _actionRipple = nullptr;
  646. }
  647. if (_actionRipple) {
  648. _actionRipple->paint(
  649. p,
  650. x + st::groupCallActiveButton.rippleAreaPosition.x(),
  651. y + st::groupCallActiveButton.rippleAreaPosition.y(),
  652. outerWidth);
  653. if (_actionRipple->empty()) {
  654. _actionRipple.reset();
  655. }
  656. }
  657. paintMuteIcon(p, iconRect);
  658. }
  659. MembersRowDelegate::IconState MembersRow::computeIconState(
  660. MembersRowStyle style) const {
  661. const auto speaking = _speakingAnimation.value(_speaking ? 1. : 0.);
  662. const auto active = _activeAnimation.value(
  663. (_state == State::Active) ? 1. : 0.);
  664. const auto muted = _mutedAnimation.value(
  665. (_state == State::Muted || _state == State::RaisedHand) ? 1. : 0.);
  666. return {
  667. .speaking = speaking,
  668. .active = active,
  669. .muted = muted,
  670. .mutedByMe = _mutedByMe,
  671. .raisedHand = (_state == State::RaisedHand),
  672. .invited = (_state == State::Invited),
  673. .style = style,
  674. };
  675. }
  676. void MembersRow::showContextMenu() {
  677. return _delegate->rowShowContextMenu(this);
  678. }
  679. void MembersRow::refreshStatus() {
  680. setCustomStatus(
  681. (_speaking
  682. ? tr::lng_group_call_active(tr::now)
  683. : _raisedHandStatus
  684. ? tr::lng_group_call_raised_hand_status(tr::now)
  685. : tr::lng_group_call_inactive(tr::now)),
  686. _speaking);
  687. }
  688. void MembersRow::rightActionAddRipple(
  689. QPoint point,
  690. Fn<void()> updateCallback) {
  691. if (!_actionRipple) {
  692. auto mask = Ui::RippleAnimation::EllipseMask(QSize(
  693. st::groupCallActiveButton.rippleAreaSize,
  694. st::groupCallActiveButton.rippleAreaSize));
  695. _actionRipple = std::make_unique<Ui::RippleAnimation>(
  696. st::groupCallActiveButton.ripple,
  697. std::move(mask),
  698. std::move(updateCallback));
  699. }
  700. _actionRipple->add(point - st::groupCallActiveButton.rippleAreaPosition);
  701. }
  702. void MembersRow::refreshName(const style::PeerListItem &st) {
  703. PeerListRow::refreshName(st);
  704. //_narrowName = Ui::Text::String();
  705. }
  706. void MembersRow::rightActionStopLastRipple() {
  707. if (_actionRipple) {
  708. _actionRipple->lastStop();
  709. }
  710. }
  711. } // namespace Calls::Group