call_mute_button.cpp 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186
  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/controls/call_mute_button.h"
  8. #include "base/flat_map.h"
  9. #include "ui/abstract_button.h"
  10. #include "ui/effects/shake_animation.h"
  11. #include "ui/paint/blobs.h"
  12. #include "ui/painter.h"
  13. #include "ui/power_saving.h"
  14. #include "ui/widgets/call_button.h"
  15. #include "ui/widgets/labels.h"
  16. #include "ui/ui_utility.h"
  17. #include "base/random.h"
  18. #include "styles/palette.h"
  19. #include "styles/style_widgets.h"
  20. #include "styles/style_calls.h"
  21. #include <QtCore/QtMath>
  22. #include <QtCore/QCoreApplication>
  23. namespace Ui {
  24. namespace {
  25. using Radiuses = Paint::Blob::Radiuses;
  26. constexpr auto kMaxLevel = 1.;
  27. constexpr auto kLevelDuration = 100. + 500. * 0.33;
  28. constexpr auto kScaleBig = 0.807 - 0.1;
  29. constexpr auto kScaleSmall = 0.704 - 0.1;
  30. constexpr auto kScaleBigMin = 0.878;
  31. constexpr auto kScaleSmallMin = 0.926;
  32. constexpr auto kScaleBigMax = (float)(kScaleBigMin + kScaleBig);
  33. constexpr auto kScaleSmallMax = (float)(kScaleSmallMin + kScaleSmall);
  34. constexpr auto kMainRadiusFactor = (float)(50. / 57.);
  35. constexpr auto kGlowPaddingFactor = 1.2;
  36. constexpr auto kGlowMinScale = 0.6;
  37. constexpr auto kGlowAlpha = 150;
  38. constexpr auto kOverrideColorBgAlpha = 76;
  39. constexpr auto kOverrideColorRippleAlpha = 50;
  40. constexpr auto kSwitchStateDuration = crl::time(120);
  41. constexpr auto kSwitchLabelDuration = crl::time(180);
  42. // Switch state from Connecting animation.
  43. constexpr auto kSwitchRadialDuration = crl::time(350);
  44. constexpr auto kSwitchCirclelDuration = crl::time(275);
  45. constexpr auto kBlobsScaleEnterDuration = crl::time(400);
  46. constexpr auto kSwitchStateFromConnectingDuration = kSwitchRadialDuration
  47. + kSwitchCirclelDuration
  48. + kBlobsScaleEnterDuration;
  49. constexpr auto kRadialEndPartAnimation = float(kSwitchRadialDuration)
  50. / kSwitchStateFromConnectingDuration;
  51. constexpr auto kBlobsWidgetPartAnimation = 1. - kRadialEndPartAnimation;
  52. constexpr auto kFillCirclePartAnimation = float(kSwitchCirclelDuration)
  53. / (kSwitchCirclelDuration + kBlobsScaleEnterDuration);
  54. constexpr auto kBlobPartAnimation = float(kBlobsScaleEnterDuration)
  55. / (kSwitchCirclelDuration + kBlobsScaleEnterDuration);
  56. constexpr auto kOverlapProgressRadialHide = 1.2;
  57. constexpr auto kRadialFinishArcShift = 1200;
  58. [[nodiscard]] CallMuteButtonType TypeForIcon(CallMuteButtonType type) {
  59. return (type == CallMuteButtonType::Connecting)
  60. ? CallMuteButtonType::Muted
  61. : (type == CallMuteButtonType::RaisedHand)
  62. ? CallMuteButtonType::ForceMuted
  63. : type;
  64. };
  65. [[nodiscard]] QSize AdjustedLottieSize(
  66. not_null<const style::CallMuteButton*> st) {
  67. const auto &button = st->active.button;
  68. const auto left = (button.width - st->lottieSize.width()) / 2;
  69. const auto size = button.width - 2 * left;
  70. return QSize(size, size);
  71. }
  72. [[nodiscard]] int AdjustedBgSize(
  73. not_null<const style::CallMuteButton*> st) {
  74. const auto &button = st->active.button;
  75. const auto left = (button.width - st->active.bgSize) / 2;
  76. return button.width - 2 * left;
  77. }
  78. [[nodiscard]] int AdjustedBgSkip(
  79. not_null<const style::CallMuteButton*> st) {
  80. const auto &button = st->active.button;
  81. const auto bgSize = AdjustedBgSize(st);
  82. return (button.width - bgSize) / 2;
  83. }
  84. auto MuteBlobs() {
  85. return std::vector<Paint::Blobs::BlobData>{
  86. {
  87. .segmentsCount = 9,
  88. .minScale = kScaleSmallMin / kScaleSmallMax,
  89. .minRadius = st::callMuteMinorBlobMinRadius
  90. * kScaleSmallMax
  91. * kMainRadiusFactor,
  92. .maxRadius = st::callMuteMinorBlobMaxRadius
  93. * kScaleSmallMax
  94. * kMainRadiusFactor,
  95. .speedScale = 1.,
  96. .alpha = (76. / 255.),
  97. },
  98. {
  99. .segmentsCount = 12,
  100. .minScale = kScaleBigMin / kScaleBigMax,
  101. .minRadius = st::callMuteMajorBlobMinRadius
  102. * kScaleBigMax
  103. * kMainRadiusFactor,
  104. .maxRadius = st::callMuteMajorBlobMaxRadius
  105. * kScaleBigMax
  106. * kMainRadiusFactor,
  107. .speedScale = 1.,
  108. .alpha = (76. / 255.),
  109. },
  110. };
  111. }
  112. auto Colors() {
  113. using Vector = std::vector<QColor>;
  114. using Colors = anim::gradient_colors;
  115. auto result = base::flat_map<CallMuteButtonType, Colors>{
  116. {
  117. CallMuteButtonType::Active,
  118. Colors(Vector{ st::groupCallLive1->c, st::groupCallLive2->c })
  119. },
  120. {
  121. CallMuteButtonType::Connecting,
  122. Colors(st::callIconBg->c)
  123. },
  124. {
  125. CallMuteButtonType::Muted,
  126. Colors(Vector{ st::groupCallMuted1->c, st::groupCallMuted2->c })
  127. },
  128. };
  129. const auto forceMutedColors = Colors(QGradientStops{
  130. { .0, st::groupCallForceMuted3->c },
  131. { .5, st::groupCallForceMuted2->c },
  132. { 1., st::groupCallForceMuted1->c } });
  133. const auto forceMutedTypes = {
  134. CallMuteButtonType::ForceMuted,
  135. CallMuteButtonType::RaisedHand,
  136. CallMuteButtonType::ScheduledCanStart,
  137. CallMuteButtonType::ScheduledNotify,
  138. CallMuteButtonType::ScheduledSilent,
  139. };
  140. for (const auto type : forceMutedTypes) {
  141. result.emplace(type, forceMutedColors);
  142. }
  143. return result;
  144. }
  145. bool IsConnecting(CallMuteButtonType type) {
  146. return (type == CallMuteButtonType::Connecting);
  147. }
  148. bool IsInactive(CallMuteButtonType type) {
  149. return IsConnecting(type);
  150. }
  151. auto Clamp(float64 value) {
  152. return std::clamp(value, 0., 1.);
  153. }
  154. void ComputeRadialFinish(
  155. int &value,
  156. float64 progress,
  157. int to = -RadialState::kFull) {
  158. value = anim::interpolate(value, to, Clamp(progress));
  159. }
  160. } // namespace
  161. class AnimatedLabel final : public RpWidget {
  162. public:
  163. AnimatedLabel(
  164. QWidget *parent,
  165. rpl::producer<QString> &&text,
  166. crl::time duration,
  167. int additionalHeight,
  168. const style::FlatLabel &st = st::defaultFlatLabel);
  169. int contentHeight() const;
  170. private:
  171. void setText(const QString &text);
  172. const style::FlatLabel &_st;
  173. const crl::time _duration;
  174. const int _additionalHeight;
  175. const TextParseOptions _options;
  176. Text::String _text;
  177. Text::String _previousText;
  178. Animations::Simple _animation;
  179. };
  180. AnimatedLabel::AnimatedLabel(
  181. QWidget *parent,
  182. rpl::producer<QString> &&text,
  183. crl::time duration,
  184. int additionalHeight,
  185. const style::FlatLabel &st)
  186. : RpWidget(parent)
  187. , _st(st)
  188. , _duration(duration)
  189. , _additionalHeight(additionalHeight)
  190. , _options({ 0, 0, 0, Qt::LayoutDirectionAuto }) {
  191. std::move(
  192. text
  193. ) | rpl::start_with_next([=](const QString &value) {
  194. setText(value);
  195. }, lifetime());
  196. paintRequest(
  197. ) | rpl::start_with_next([=] {
  198. Painter p(this);
  199. const auto progress = _animation.value(1.);
  200. p.setFont(_st.style.font);
  201. p.setPen(_st.textFg);
  202. p.setTextPalette(_st.palette);
  203. const auto textHeight = contentHeight();
  204. const auto diffHeight = height() - textHeight;
  205. const auto center = diffHeight / 2;
  206. p.setOpacity(1. - progress);
  207. if (p.opacity()) {
  208. _previousText.draw(
  209. p,
  210. 0,
  211. anim::interpolate(center, diffHeight, progress),
  212. width(),
  213. style::al_center);
  214. }
  215. p.setOpacity(progress);
  216. if (p.opacity()) {
  217. _text.draw(
  218. p,
  219. 0,
  220. anim::interpolate(0, center, progress),
  221. width(),
  222. style::al_center);
  223. }
  224. }, lifetime());
  225. }
  226. int AnimatedLabel::contentHeight() const {
  227. return _st.style.font->height;
  228. }
  229. void AnimatedLabel::setText(const QString &text) {
  230. if (_text.toString() == text) {
  231. return;
  232. }
  233. _previousText = std::move(_text);
  234. _text = Ui::Text::String(_st.style, text, _options);
  235. const auto width = std::max(
  236. _st.style.font->width(_text.toString()),
  237. _st.style.font->width(_previousText.toString()));
  238. resize(
  239. width + _additionalHeight,
  240. contentHeight() + _additionalHeight * 2);
  241. _animation.stop();
  242. _animation.start([=] { update(); }, 0., 1., _duration);
  243. }
  244. class BlobsWidget final : public RpWidget {
  245. public:
  246. BlobsWidget(
  247. not_null<RpWidget*> parent,
  248. int diameter,
  249. rpl::producer<bool> &&hideBlobs);
  250. void setDiameter(int diameter);
  251. void setLevel(float level);
  252. void setBlobBrush(QBrush brush);
  253. void setGlowBrush(QBrush brush);
  254. [[nodiscard]] QRectF innerRect() const;
  255. [[nodiscard]] float64 switchConnectingProgress() const;
  256. void setSwitchConnectingProgress(float64 progress);
  257. private:
  258. void init(int diameter);
  259. void computeCircleRect();
  260. Paint::Blobs _blobs;
  261. float _circleRadius = 0.;
  262. QBrush _blobBrush;
  263. QBrush _glowBrush;
  264. int _center = 0;
  265. QRectF _circleRect;
  266. float64 _switchConnectingProgress = 0.;
  267. crl::time _blobsLastTime = 0;
  268. crl::time _blobsHideLastTime = 0;
  269. float64 _blobsScaleEnter = 0.;
  270. crl::time _blobsScaleLastTime = 0;
  271. bool _hideBlobs = true;
  272. Animations::Basic _animation;
  273. };
  274. BlobsWidget::BlobsWidget(
  275. not_null<RpWidget*> parent,
  276. int diameter,
  277. rpl::producer<bool> &&hideBlobs)
  278. : RpWidget(parent)
  279. , _blobs(MuteBlobs(), kLevelDuration, kMaxLevel)
  280. , _blobBrush(Qt::transparent)
  281. , _glowBrush(Qt::transparent)
  282. , _blobsLastTime(crl::now())
  283. , _blobsScaleLastTime(crl::now()) {
  284. init(diameter);
  285. std::move(
  286. hideBlobs
  287. ) | rpl::start_with_next([=](bool hide) {
  288. if (_hideBlobs != hide) {
  289. const auto now = crl::now();
  290. if ((now - _blobsScaleLastTime) >= kBlobsScaleEnterDuration) {
  291. _blobsScaleLastTime = now;
  292. }
  293. _hideBlobs = hide;
  294. }
  295. if (hide) {
  296. setLevel(0.);
  297. }
  298. _blobsHideLastTime = hide ? crl::now() : 0;
  299. if (!hide && !_animation.animating()) {
  300. _animation.start();
  301. }
  302. }, lifetime());
  303. }
  304. void BlobsWidget::setDiameter(int diameter) {
  305. _circleRadius = diameter / 2.;
  306. const auto defaultSize = _blobs.maxRadius() * 2 * kGlowPaddingFactor;
  307. const auto s = int(std::ceil((defaultSize * diameter)
  308. / float64(st::callMuteBlobRadiusForDiameter)));
  309. const auto size = QSize{ s, s };
  310. if (this->size() != size) {
  311. resize(size);
  312. }
  313. computeCircleRect();
  314. }
  315. void BlobsWidget::computeCircleRect() {
  316. const auto &r = _circleRadius;
  317. const auto left = (size().width() - r * 2.) / 2.;
  318. const auto add = st::callConnectingRadial.thickness / 2;
  319. _circleRect = QRectF(left, left, r * 2, r * 2).marginsAdded(
  320. style::margins(add, add, add, add));
  321. }
  322. void BlobsWidget::init(int diameter) {
  323. setAttribute(Qt::WA_TransparentForMouseEvents);
  324. const auto cutRect = [](Painter &p, const QRectF &r) {
  325. p.save();
  326. p.setOpacity(1.);
  327. p.setBrush(st::groupCallBg);
  328. p.setCompositionMode(QPainter::CompositionMode_Source);
  329. p.drawEllipse(r);
  330. p.restore();
  331. };
  332. setDiameter(diameter);
  333. sizeValue(
  334. ) | rpl::start_with_next([=](QSize size) {
  335. _center = size.width() / 2;
  336. computeCircleRect();
  337. }, lifetime());
  338. paintRequest(
  339. ) | rpl::start_with_next([=] {
  340. Painter p(this);
  341. PainterHighQualityEnabler hq(p);
  342. p.setPen(Qt::NoPen);
  343. // Glow.
  344. const auto s = kGlowMinScale
  345. + (1. - kGlowMinScale) * _blobs.currentLevel();
  346. p.translate(_center, _center);
  347. p.scale(s, s);
  348. p.translate(-_center, -_center);
  349. p.fillRect(rect(), _glowBrush);
  350. p.resetTransform();
  351. // Blobs.
  352. p.translate(_center, _center);
  353. const auto scale = (_switchConnectingProgress > 0.)
  354. ? anim::easeOutBack(
  355. 1.,
  356. _blobsScaleEnter * (1. - Clamp(
  357. _switchConnectingProgress / kBlobPartAnimation)))
  358. : _blobsScaleEnter;
  359. const auto sizeScale = (2. * _circleRadius)
  360. / st::callMuteBlobRadiusForDiameter;
  361. _blobs.paint(p, _blobBrush, scale * sizeScale);
  362. p.translate(-_center, -_center);
  363. if (scale < 1.) {
  364. cutRect(p, _circleRect);
  365. }
  366. // Main circle.
  367. const auto circleProgress
  368. = Clamp(_switchConnectingProgress - kBlobPartAnimation)
  369. / kFillCirclePartAnimation;
  370. const auto skipColoredCircle = (circleProgress == 1.);
  371. if (!skipColoredCircle) {
  372. p.setBrush(_blobBrush);
  373. p.drawEllipse(_circleRect);
  374. }
  375. if (_switchConnectingProgress > 0.) {
  376. p.resetTransform();
  377. const auto mF = (_circleRect.width() / 2) * (1. - circleProgress);
  378. const auto cutOutRect = _circleRect.marginsRemoved(
  379. QMarginsF(mF, mF, mF, mF));
  380. if (!skipColoredCircle) {
  381. p.setBrush(st::callConnectingRadial.color);
  382. p.setOpacity(circleProgress);
  383. p.drawEllipse(_circleRect);
  384. }
  385. p.setOpacity(1.);
  386. cutRect(p, cutOutRect);
  387. p.setBrush(st::callIconBg);
  388. p.drawEllipse(cutOutRect);
  389. }
  390. }, lifetime());
  391. _animation.init([=](crl::time now) {
  392. if (const auto &last = _blobsHideLastTime; (last > 0)
  393. && (now - last >= kBlobsScaleEnterDuration)) {
  394. _animation.stop();
  395. return false;
  396. }
  397. _blobs.updateLevel(now - _blobsLastTime);
  398. _blobsLastTime = now;
  399. const auto dt = Clamp(
  400. (now - _blobsScaleLastTime) / float64(kBlobsScaleEnterDuration));
  401. _blobsScaleEnter = _hideBlobs
  402. ? (1. - anim::easeInCirc(1., dt))
  403. : anim::easeOutBack(1., dt);
  404. update();
  405. return true;
  406. });
  407. shownValue(
  408. ) | rpl::start_with_next([=](bool shown) {
  409. if (shown) {
  410. _animation.start();
  411. } else {
  412. _animation.stop();
  413. }
  414. }, lifetime());
  415. }
  416. QRectF BlobsWidget::innerRect() const {
  417. return _circleRect;
  418. }
  419. void BlobsWidget::setBlobBrush(QBrush brush) {
  420. if (_blobBrush == brush) {
  421. return;
  422. }
  423. _blobBrush = brush;
  424. }
  425. void BlobsWidget::setGlowBrush(QBrush brush) {
  426. if (_glowBrush == brush) {
  427. return;
  428. }
  429. _glowBrush = brush;
  430. }
  431. void BlobsWidget::setLevel(float level) {
  432. if (_blobsHideLastTime) {
  433. return;
  434. }
  435. _blobs.setLevel(level);
  436. }
  437. float64 BlobsWidget::switchConnectingProgress() const {
  438. return _switchConnectingProgress;
  439. }
  440. void BlobsWidget::setSwitchConnectingProgress(float64 progress) {
  441. _switchConnectingProgress = progress;
  442. }
  443. CallMuteButton::CallMuteButton(
  444. not_null<RpWidget*> parent,
  445. const style::CallMuteButton &st,
  446. rpl::producer<bool> &&hideBlobs,
  447. CallMuteButtonState initial)
  448. : _state(initial)
  449. , _st(&st)
  450. , _lottieSize(AdjustedLottieSize(_st))
  451. , _bgSize(AdjustedBgSize(_st))
  452. , _bgSkip(AdjustedBgSkip(_st))
  453. , _blobs(base::make_unique_q<BlobsWidget>(
  454. parent,
  455. _bgSize,
  456. rpl::combine(
  457. PowerSaving::OnValue(PowerSaving::kCalls),
  458. std::move(hideBlobs),
  459. _state.value(
  460. ) | rpl::map([](const CallMuteButtonState &state) {
  461. return IsInactive(state.type);
  462. })
  463. ) | rpl::map(rpl::mappers::_1 || rpl::mappers::_2 || rpl::mappers::_3)))
  464. , _content(base::make_unique_q<AbstractButton>(parent))
  465. , _colors(Colors())
  466. , _iconState(iconStateFrom(initial.type)) {
  467. init();
  468. }
  469. void CallMuteButton::refreshLabels() {
  470. _centerLabel = base::make_unique_q<AnimatedLabel>(
  471. _content->parentWidget(),
  472. _state.value(
  473. ) | rpl::map([](const CallMuteButtonState &state) {
  474. return state.subtext.isEmpty() ? state.text : QString();
  475. }),
  476. kSwitchLabelDuration,
  477. _st->labelAdditional,
  478. _st->active.label);
  479. _label = base::make_unique_q<AnimatedLabel>(
  480. _content->parentWidget(),
  481. _state.value(
  482. ) | rpl::map([](const CallMuteButtonState &state) {
  483. return state.subtext.isEmpty() ? QString() : state.text;
  484. }),
  485. kSwitchLabelDuration,
  486. _st->labelAdditional,
  487. _st->active.label);
  488. _sublabel = base::make_unique_q<AnimatedLabel>(
  489. _content->parentWidget(),
  490. _state.value(
  491. ) | rpl::map([](const CallMuteButtonState &state) {
  492. return state.subtext;
  493. }),
  494. kSwitchLabelDuration,
  495. _st->labelAdditional,
  496. _st->sublabel);
  497. _label->show();
  498. rpl::combine(
  499. _content->geometryValue(),
  500. _label->sizeValue()
  501. ) | rpl::start_with_next([=](QRect my, QSize size) {
  502. updateLabelGeometry(my, size);
  503. }, _label->lifetime());
  504. _label->setAttribute(Qt::WA_TransparentForMouseEvents);
  505. _sublabel->show();
  506. rpl::combine(
  507. _content->geometryValue(),
  508. _sublabel->sizeValue()
  509. ) | rpl::start_with_next([=](QRect my, QSize size) {
  510. updateSublabelGeometry(my, size);
  511. }, _sublabel->lifetime());
  512. _sublabel->setAttribute(Qt::WA_TransparentForMouseEvents);
  513. _centerLabel->show();
  514. rpl::combine(
  515. _content->geometryValue(),
  516. _centerLabel->sizeValue()
  517. ) | rpl::start_with_next([=](QRect my, QSize size) {
  518. updateCenterLabelGeometry(my, size);
  519. }, _centerLabel->lifetime());
  520. _centerLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
  521. }
  522. void CallMuteButton::refreshIcons() {
  523. _icons[0].emplace(Lottie::IconDescriptor{
  524. .path = u":/icons/calls/voice.lottie"_q,
  525. .color = &st::groupCallIconFg,
  526. .sizeOverride = _lottieSize,
  527. .frame = (_iconState.index ? 0 : _iconState.frameTo),
  528. });
  529. _icons[1].emplace(Lottie::IconDescriptor{
  530. .path = u":/icons/calls/hands.lottie"_q,
  531. .color = &st::groupCallIconFg,
  532. .sizeOverride = _lottieSize,
  533. .frame = (_iconState.index ? _iconState.frameTo : 0),
  534. });
  535. }
  536. auto CallMuteButton::iconStateAnimated(CallMuteButtonType previous)
  537. -> IconState {
  538. using Type = CallMuteButtonType;
  539. using Key = std::pair<Type, Type>;
  540. struct Animation {
  541. int from = 0;
  542. int to = 0;
  543. };
  544. static const auto kAnimations = std::vector<std::pair<Key, Animation>>{
  545. { { Type::ForceMuted, Type::Muted }, { 0, 35 } },
  546. { { Type::Muted, Type::Active }, { 36, 68 } },
  547. { { Type::Active, Type::Muted }, { 69, 98 } },
  548. { { Type::Muted, Type::ForceMuted }, { 99, 135 } },
  549. { { Type::Active, Type::ForceMuted }, { 136, 172 } },
  550. { { Type::ScheduledSilent, Type::ScheduledNotify }, { 173, 201 } },
  551. { { Type::ScheduledSilent, Type::Muted }, { 202, 236 } },
  552. { { Type::ScheduledSilent, Type::ForceMuted }, { 237, 273 } },
  553. { { Type::ScheduledNotify, Type::ForceMuted }, { 274, 310 } },
  554. { { Type::ScheduledNotify, Type::ScheduledSilent }, { 311, 343 } },
  555. { { Type::ScheduledNotify, Type::Muted }, { 344, 375 } },
  556. { { Type::ScheduledCanStart, Type::Muted }, { 376, 403 } },
  557. };
  558. static const auto kMap = [] {
  559. // flat_multi_map_pair_type lacks some required constructors :(
  560. auto &&list = kAnimations | ranges::views::transform([](auto &&pair) {
  561. return base::flat_multi_map_pair_type<Key, Animation>(
  562. pair.first,
  563. pair.second);
  564. });
  565. return base::flat_map<Key, Animation>(begin(list), end(list));
  566. }();
  567. const auto was = TypeForIcon(previous);
  568. const auto now = TypeForIcon(_state.current().type);
  569. if (was == now) {
  570. return {};
  571. }
  572. if (const auto i = kMap.find(Key{ was, now }); i != end(kMap)) {
  573. return { 0, i->second.from, i->second.to };
  574. }
  575. return {};
  576. }
  577. CallMuteButton::IconState CallMuteButton::iconStateFrom(
  578. CallMuteButtonType previous) {
  579. if (const auto animated = iconStateAnimated(previous)) {
  580. return animated;
  581. }
  582. using Type = CallMuteButtonType;
  583. static const auto kFinal = base::flat_map<Type, int>{
  584. { Type::ForceMuted, 0 },
  585. { Type::Muted, 36 },
  586. { Type::Active, 69 },
  587. { Type::ScheduledSilent, 173 },
  588. { Type::ScheduledNotify, 274 },
  589. { Type::ScheduledCanStart, 376 },
  590. };
  591. const auto now = TypeForIcon(_state.current().type);
  592. const auto i = kFinal.find(now);
  593. Ensures(i != end(kFinal));
  594. return { 0, i->second, i->second };
  595. }
  596. CallMuteButton::IconState CallMuteButton::randomWavingState() {
  597. struct Animation {
  598. int from = 0;
  599. int to = 0;
  600. };
  601. static const auto kAnimations = std::vector<Animation>{
  602. { 0, 119 },
  603. { 120, 239 },
  604. { 240, 419 },
  605. { 420, 539 },
  606. };
  607. const auto index = base::RandomIndex(kAnimations.size());
  608. return { 1, kAnimations[index].from, kAnimations[index].to };
  609. }
  610. void CallMuteButton::init() {
  611. refreshLabels();
  612. refreshIcons();
  613. const auto &button = _st->active.button;
  614. _content->resize(button.width, button.height);
  615. _content->events(
  616. ) | rpl::start_with_next([=](not_null<QEvent*> e) {
  617. if (e->type() == QEvent::MouseMove) {
  618. if (!_state.current().tooltip.isEmpty()) {
  619. Ui::Tooltip::Show(1000, this);
  620. }
  621. } else if (e->type() == QEvent::Leave) {
  622. Ui::Tooltip::Hide();
  623. }
  624. }, _content->lifetime());
  625. rpl::combine(
  626. _radialInfo.rawShowProgress.value(),
  627. anim::Disables()
  628. ) | rpl::start_with_next([=](float64 value, bool disabled) {
  629. auto &info = _radialInfo;
  630. info.realShowProgress = (1. - value) / kRadialEndPartAnimation;
  631. const auto guard = gsl::finally([&] {
  632. _content->update();
  633. });
  634. if (((value == 0.) || disabled) && _radial) {
  635. _radial->stop();
  636. _radial = nullptr;
  637. return;
  638. }
  639. if ((value > 0.) && !disabled && !_radial) {
  640. _radial = std::make_unique<InfiniteRadialAnimation>(
  641. [=] { _content->update(); },
  642. _radialInfo.st);
  643. _radial->start();
  644. }
  645. if ((info.realShowProgress < 1.) && !info.isDirectionToShow) {
  646. if (_radial) {
  647. _radial->stop(anim::type::instant);
  648. _radial->start();
  649. }
  650. info.state = std::nullopt;
  651. return;
  652. }
  653. if (value == 1.) {
  654. info.state = std::nullopt;
  655. } else {
  656. if (_radial && !info.state.has_value()) {
  657. info.state = _radial->computeState();
  658. }
  659. }
  660. }, lifetime());
  661. // State type.
  662. const auto previousType
  663. = lifetime().make_state<CallMuteButtonType>(_state.current().type);
  664. setHandleMouseState(HandleMouseState::Disabled);
  665. refreshGradients();
  666. _state.value(
  667. ) | rpl::map([](const CallMuteButtonState &state) {
  668. return state.type;
  669. }) | rpl::start_with_next([=](CallMuteButtonType type) {
  670. const auto previous = *previousType;
  671. *previousType = type;
  672. const auto mouseState = HandleMouseStateFromType(type);
  673. setHandleMouseState(HandleMouseState::Disabled);
  674. if (mouseState != HandleMouseState::Enabled) {
  675. setHandleMouseState(mouseState);
  676. }
  677. const auto fromConnecting = IsConnecting(previous);
  678. const auto toConnecting = IsConnecting(type);
  679. const auto radialShowFrom = fromConnecting ? 1. : 0.;
  680. const auto radialShowTo = toConnecting ? 1. : 0.;
  681. const auto from = (_switchAnimation.animating() && !fromConnecting)
  682. ? (1. - _switchAnimation.value(0.))
  683. : 0.;
  684. const auto to = 1.;
  685. _radialInfo.isDirectionToShow = fromConnecting;
  686. scheduleIconState(iconStateFrom(previous));
  687. auto callback = [=](float64 value) {
  688. const auto brushProgress = fromConnecting ? 1. : value;
  689. _blobs->setBlobBrush(QBrush(
  690. _linearGradients.gradient(previous, type, brushProgress)));
  691. _blobs->setGlowBrush(QBrush(
  692. _glowGradients.gradient(previous, type, value)));
  693. _blobs->update();
  694. const auto radialShowProgress = (radialShowFrom == radialShowTo)
  695. ? radialShowTo
  696. : anim::interpolateToF(radialShowFrom, radialShowTo, value);
  697. if (radialShowProgress != _radialInfo.rawShowProgress.current()) {
  698. _radialInfo.rawShowProgress = radialShowProgress;
  699. _blobs->setSwitchConnectingProgress(Clamp(
  700. radialShowProgress / kBlobsWidgetPartAnimation));
  701. }
  702. overridesColors(previous, type, value);
  703. if (value == to) {
  704. setHandleMouseState(mouseState);
  705. }
  706. };
  707. _switchAnimation.stop();
  708. const auto duration = (1. - from) * ((fromConnecting || toConnecting)
  709. ? kSwitchStateFromConnectingDuration
  710. : kSwitchStateDuration);
  711. _switchAnimation.start(std::move(callback), from, to, duration);
  712. }, lifetime());
  713. // Icon rect.
  714. _content->sizeValue(
  715. ) | rpl::start_with_next([=](QSize size) {
  716. const auto icon = _lottieSize;
  717. _muteIconRect = QRect(
  718. (size.width() - icon.width()) / 2,
  719. _st->lottieTop,
  720. icon.width(),
  721. icon.height());
  722. }, lifetime());
  723. // Paint.
  724. _content->paintRequest(
  725. ) | rpl::start_with_next([=](QRect clip) {
  726. Painter p(_content);
  727. const auto expand = _state.current().expandType;
  728. if (expand == CallMuteButtonExpandType::Expanded) {
  729. st::callMuteFromFullScreen.paintInCenter(p, _muteIconRect);
  730. } else if (expand == CallMuteButtonExpandType::Normal) {
  731. st::callMuteToFullScreen.paintInCenter(p, _muteIconRect);
  732. } else {
  733. _icons[_iconState.index]->paint(
  734. p,
  735. _muteIconRect.x(),
  736. _muteIconRect.y());
  737. }
  738. if (_radialInfo.state.has_value() && _switchAnimation.animating()) {
  739. const auto radialProgress = _radialInfo.realShowProgress;
  740. auto r = *_radialInfo.state;
  741. r.shown = 1.;
  742. if (_radialInfo.isDirectionToShow) {
  743. const auto to = r.arcFrom - kRadialFinishArcShift;
  744. ComputeRadialFinish(r.arcFrom, radialProgress, to);
  745. ComputeRadialFinish(r.arcLength, radialProgress);
  746. } else {
  747. r.arcLength = RadialState::kFull;
  748. }
  749. const auto opacity = (radialProgress > kOverlapProgressRadialHide)
  750. ? 0.
  751. : _blobs->switchConnectingProgress();
  752. p.setOpacity(opacity);
  753. InfiniteRadialAnimation::Draw(
  754. p,
  755. r,
  756. QPoint(_bgSkip, _bgSkip),
  757. QSize(_bgSize, _bgSize),
  758. _content->width(),
  759. QPen(_radialInfo.st.color),
  760. _radialInfo.st.thickness);
  761. } else if (_radial) {
  762. auto state = _radial->computeState();
  763. state.shown = 1.;
  764. InfiniteRadialAnimation::Draw(
  765. p,
  766. std::move(state),
  767. QPoint(_bgSkip, _bgSkip),
  768. QSize(_bgSize, _bgSize),
  769. _content->width(),
  770. QPen(_radialInfo.st.color),
  771. _radialInfo.st.thickness);
  772. }
  773. }, _content->lifetime());
  774. }
  775. void CallMuteButton::refreshGradients() {
  776. const auto blobsInner = [&] {
  777. // The point of the circle at 45 degrees.
  778. const auto w = _blobs->innerRect().width();
  779. const auto mF = (1 - std::cos(M_PI / 4.)) * (w / 2.);
  780. return _blobs->innerRect().marginsRemoved(QMarginsF(mF, mF, mF, mF));
  781. }();
  782. _linearGradients = anim::linear_gradients<CallMuteButtonType>(
  783. _colors,
  784. QPointF(blobsInner.x() + blobsInner.width(), blobsInner.y()),
  785. QPointF(blobsInner.x(), blobsInner.y() + blobsInner.height()));
  786. auto glowColors = [&] {
  787. auto copy = _colors;
  788. for (auto &[type, stops] : copy) {
  789. auto firstColor = IsInactive(type)
  790. ? st::groupCallBg->c
  791. : stops.stops[(stops.stops.size() - 1) / 2].second;
  792. firstColor.setAlpha(kGlowAlpha);
  793. stops.stops = QGradientStops{
  794. { 0., std::move(firstColor) },
  795. { 1., QColor(Qt::transparent) }
  796. };
  797. }
  798. return copy;
  799. }();
  800. _glowGradients = anim::radial_gradients<CallMuteButtonType>(
  801. std::move(glowColors),
  802. blobsInner.center(),
  803. _blobs->width() / 2);
  804. }
  805. void CallMuteButton::scheduleIconState(const IconState &state) {
  806. if (_iconState != state) {
  807. if (_icons[_iconState.index]->animating()) {
  808. _scheduledState = state;
  809. } else {
  810. startIconState(state);
  811. }
  812. } else if (_scheduledState) {
  813. _scheduledState = std::nullopt;
  814. }
  815. }
  816. void CallMuteButton::startIconState(const IconState &state) {
  817. _iconState = state;
  818. _scheduledState = std::nullopt;
  819. _icons[_iconState.index]->animate(
  820. [=] { iconAnimationCallback(); },
  821. _iconState.frameFrom,
  822. _iconState.frameTo);
  823. }
  824. void CallMuteButton::iconAnimationCallback() {
  825. _content->update(_muteIconRect);
  826. if (!_icons[_iconState.index]->animating() && _scheduledState) {
  827. startIconState(*_scheduledState);
  828. }
  829. }
  830. QString CallMuteButton::tooltipText() const {
  831. return _state.current().tooltip;
  832. }
  833. QPoint CallMuteButton::tooltipPos() const {
  834. return QCursor::pos();
  835. }
  836. bool CallMuteButton::tooltipWindowActive() const {
  837. return Ui::AppInFocus()
  838. && Ui::InFocusChain(_content->window())
  839. && _content->mapToGlobal(_content->rect()).contains(QCursor::pos());
  840. }
  841. const style::Tooltip *CallMuteButton::tooltipSt() const {
  842. return &st::groupCallTooltip;
  843. }
  844. void CallMuteButton::updateLabelsGeometry() {
  845. updateLabelGeometry(_content->geometry(), _label->size());
  846. updateCenterLabelGeometry(_content->geometry(), _centerLabel->size());
  847. updateSublabelGeometry(_content->geometry(), _sublabel->size());
  848. }
  849. void CallMuteButton::updateLabelGeometry(QRect my, QSize size) {
  850. const auto skip = _st->sublabelSkip + _st->labelsSkip;
  851. const auto contentHeight = _label->contentHeight();
  852. const auto contentTop = my.y() + my.height() - contentHeight - skip;
  853. _label->moveToLeft(
  854. my.x() + (my.width() - size.width()) / 2 + _labelShakeShift,
  855. contentTop - (size.height() - contentHeight) / 2,
  856. my.width());
  857. }
  858. void CallMuteButton::updateCenterLabelGeometry(QRect my, QSize size) {
  859. const auto skip = (_st->sublabelSkip / 2) + _st->labelsSkip;
  860. const auto contentHeight = _centerLabel->contentHeight();
  861. const auto contentTop = my.y() + my.height() - contentHeight - skip;
  862. _centerLabel->moveToLeft(
  863. my.x() + (my.width() - size.width()) / 2 + _labelShakeShift,
  864. contentTop - (size.height() - contentHeight) / 2,
  865. my.width());
  866. }
  867. void CallMuteButton::updateSublabelGeometry(QRect my, QSize size) {
  868. const auto skip = _st->labelsSkip;
  869. const auto contentHeight = _sublabel->contentHeight();
  870. const auto contentTop = my.y() + my.height() - contentHeight - skip;
  871. _sublabel->moveToLeft(
  872. my.x() + (my.width() - size.width()) / 2 + _labelShakeShift,
  873. contentTop - (size.height() - contentHeight) / 2,
  874. my.width());
  875. }
  876. void CallMuteButton::shake() {
  877. if (_shakeAnimation.animating()) {
  878. return;
  879. }
  880. _shakeAnimation.start(DefaultShakeCallback([=](int shift) {
  881. _labelShakeShift = shift;
  882. updateLabelsGeometry();
  883. }), 0., 1., st::shakeDuration);
  884. }
  885. CallMuteButton::HandleMouseState CallMuteButton::HandleMouseStateFromType(
  886. CallMuteButtonType type) {
  887. switch (type) {
  888. case CallMuteButtonType::Active:
  889. case CallMuteButtonType::Muted:
  890. return HandleMouseState::Enabled;
  891. case CallMuteButtonType::Connecting:
  892. return HandleMouseState::Disabled;
  893. case CallMuteButtonType::ScheduledCanStart:
  894. case CallMuteButtonType::ScheduledNotify:
  895. case CallMuteButtonType::ScheduledSilent:
  896. case CallMuteButtonType::ForceMuted:
  897. case CallMuteButtonType::RaisedHand:
  898. return HandleMouseState::Enabled;
  899. }
  900. Unexpected("Type in HandleMouseStateFromType.");
  901. }
  902. void CallMuteButton::setStyle(const style::CallMuteButton &st) {
  903. if (_st == &st) {
  904. return;
  905. }
  906. _st = &st;
  907. _lottieSize = AdjustedLottieSize(_st);
  908. _bgSize = AdjustedBgSize(_st);
  909. _bgSkip = AdjustedBgSkip(_st);
  910. const auto &button = _st->active.button;
  911. _content->resize(button.width, button.height);
  912. _blobs->setDiameter(_st->active.bgSize);
  913. refreshIcons();
  914. refreshLabels();
  915. updateLabelsGeometry();
  916. refreshGradients();
  917. }
  918. void CallMuteButton::setState(const CallMuteButtonState &state) {
  919. _state = state;
  920. }
  921. void CallMuteButton::setLevel(float level) {
  922. _level = level;
  923. _blobs->setLevel(level);
  924. }
  925. rpl::producer<Qt::MouseButton> CallMuteButton::clicks() {
  926. return _content->clicks() | rpl::before_next([=] {
  927. const auto type = _state.current().type;
  928. if (type == CallMuteButtonType::ForceMuted
  929. || type == CallMuteButtonType::RaisedHand) {
  930. scheduleIconState(randomWavingState());
  931. }
  932. });
  933. }
  934. QSize CallMuteButton::innerSize() const {
  935. return QSize(
  936. _content->width() - 2 * _bgSkip,
  937. _content->width() - 2 * _bgSkip);
  938. }
  939. void CallMuteButton::moveInner(QPoint position) {
  940. _content->move(position - QPoint(_bgSkip, _bgSkip));
  941. {
  942. const auto offset = QPoint(
  943. (_blobs->width() - _content->width()) / 2,
  944. (_blobs->height() - _content->width()) / 2);
  945. _blobs->move(_content->pos() - offset);
  946. }
  947. }
  948. void CallMuteButton::setVisible(bool visible) {
  949. _centerLabel->setVisible(visible);
  950. _label->setVisible(visible);
  951. _sublabel->setVisible(visible);
  952. _content->setVisible(visible);
  953. _blobs->setVisible(visible);
  954. }
  955. bool CallMuteButton::isHidden() const {
  956. return _content->isHidden();
  957. }
  958. void CallMuteButton::raise() {
  959. _blobs->raise();
  960. _content->raise();
  961. _centerLabel->raise();
  962. _label->raise();
  963. _sublabel->raise();
  964. }
  965. void CallMuteButton::lower() {
  966. _centerLabel->lower();
  967. _label->lower();
  968. _sublabel->lower();
  969. _content->lower();
  970. _blobs->lower();
  971. }
  972. void CallMuteButton::setHandleMouseState(HandleMouseState state) {
  973. if (_handleMouseState == state) {
  974. return;
  975. }
  976. _handleMouseState = state;
  977. const auto handle = (_handleMouseState != HandleMouseState::Disabled);
  978. const auto pointer = (_handleMouseState == HandleMouseState::Enabled);
  979. _content->setAttribute(Qt::WA_TransparentForMouseEvents, !handle);
  980. _content->setPointerCursor(pointer);
  981. }
  982. void CallMuteButton::overridesColors(
  983. CallMuteButtonType fromType,
  984. CallMuteButtonType toType,
  985. float64 progress) {
  986. const auto toInactive = IsInactive(toType);
  987. const auto fromInactive = IsInactive(fromType);
  988. if (toInactive && (progress == 1)) {
  989. _colorOverrides = CallButtonColors();
  990. return;
  991. }
  992. const auto &fromStops = _colors.find(fromType)->second.stops;
  993. const auto &toStops = _colors.find(toType)->second.stops;
  994. auto from = fromStops[(fromStops.size() - 1) / 2].second;
  995. auto to = toStops[(toStops.size() - 1) / 2].second;
  996. auto fromRipple = from;
  997. auto toRipple = to;
  998. if (!toInactive) {
  999. toRipple.setAlpha(kOverrideColorRippleAlpha);
  1000. to.setAlpha(kOverrideColorBgAlpha);
  1001. }
  1002. if (!fromInactive) {
  1003. fromRipple.setAlpha(kOverrideColorRippleAlpha);
  1004. from.setAlpha(kOverrideColorBgAlpha);
  1005. }
  1006. const auto resultBg = anim::color(from, to, progress);
  1007. const auto resultRipple = anim::color(fromRipple, toRipple, progress);
  1008. _colorOverrides = CallButtonColors{ resultBg, resultRipple };
  1009. }
  1010. rpl::producer<CallButtonColors> CallMuteButton::colorOverrides() const {
  1011. return _colorOverrides.value();
  1012. }
  1013. not_null<RpWidget*> CallMuteButton::outer() const {
  1014. return _content.get();
  1015. }
  1016. rpl::lifetime &CallMuteButton::lifetime() {
  1017. return _blobs->lifetime();
  1018. }
  1019. CallMuteButton::~CallMuteButton() = default;
  1020. } // namespace Ui