diff --git a/Telegram/SourceFiles/calls/calls_group_members.cpp b/Telegram/SourceFiles/calls/calls_group_members.cpp index 9acddb3b3e..f7ba37e174 100644 --- a/Telegram/SourceFiles/calls/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/calls_group_members.cpp @@ -417,6 +417,7 @@ auto Row::generatePaintUserpicCallback() -> PaintRoundImageCallback { return [=](Painter &p, int x, int y, int outerWidth, int size) mutable { if (_blobsAnimation) { const auto shift = QPointF(x + size / 2., y + size / 2.); + auto hq = PainterHighQualityEnabler(p); p.translate(shift); _blobsAnimation->blobs.paint(p, st::groupCallMemberActiveStatus); p.translate(-shift); diff --git a/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp index 6718e5f194..e3be07c561 100644 --- a/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp +++ b/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp @@ -141,6 +141,7 @@ rpl::producer GroupCallTracker::ContentByCall( state->someUserpicsNotLoaded = false; using User = Ui::GroupCallBarContent::User; for (auto &userpic : state->userpics) { + userpic.peer->loadUserpic(); const auto pic = userpic.peer->genUserpic(userpic.view, st.size); userpic.uniqueKey = userpic.peer->userpicUniqueKey(userpic.view); state->current.users.push_back({ diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index d65f8d8852..a2b4a256d2 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -797,9 +797,11 @@ historyCommentsOpenOutSelected: icon {{ "history_comments_open", msgFileThumbLin historySlowmodeCounterMargins: margins(0px, 0px, 10px, 0px); -historyGroupCallUserpicSize: 36px; +historyGroupCallUserpicSize: 32px; historyGroupCallUserpicShift: 12px; -historyGroupCallUserpicStroke: 2px; +historyGroupCallUserpicStroke: 4px; +historyGroupCallBlobMinRadius: 23px; +historyGroupCallBlobMaxRadius: 25px; largeEmojiSize: 36px; largeEmojiOutline: 1px; diff --git a/Telegram/SourceFiles/ui/chat/group_call_bar.cpp b/Telegram/SourceFiles/ui/chat/group_call_bar.cpp index c7191d83a8..a244b4bbc0 100644 --- a/Telegram/SourceFiles/ui/chat/group_call_bar.cpp +++ b/Telegram/SourceFiles/ui/chat/group_call_bar.cpp @@ -10,7 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/message_bar.h" #include "ui/widgets/shadow.h" #include "ui/widgets/buttons.h" +#include "ui/paint/blobs.h" #include "lang/lang_keys.h" +#include "base/openssl_help.h" #include "styles/style_chat.h" #include "styles/style_calls.h" #include "styles/style_info.h" // st::topBarArrowPadding, like TopBarWidget. @@ -25,8 +27,66 @@ constexpr auto kDuration = 160; constexpr auto kMaxUserpics = 4; constexpr auto kWideScale = 5; +constexpr auto kBlobsEnterDuration = crl::time(250); +constexpr auto kLevelDuration = 100. + 500. * 0.23; +constexpr auto kBlobScale = 0.605; +constexpr auto kMinorBlobFactor = 0.9f; +constexpr auto kUserpicMinScale = 0.8; +constexpr auto kMaxLevel = 1.; +constexpr auto kSendRandomLevelInterval = crl::time(100); + +auto Blobs()->std::array { + return { { + { + .segmentsCount = 6, + .minScale = kBlobScale * kMinorBlobFactor, + .minRadius = st::historyGroupCallBlobMinRadius * kMinorBlobFactor, + .maxRadius = st::historyGroupCallBlobMaxRadius * kMinorBlobFactor, + .speedScale = 1., + .alpha = .5, + }, + { + .segmentsCount = 8, + .minScale = kBlobScale, + .minRadius = (float)st::historyGroupCallBlobMinRadius, + .maxRadius = (float)st::historyGroupCallBlobMaxRadius, + .speedScale = 1., + .alpha = .2, + }, + } }; +} + } // namespace +struct GroupCallBar::BlobsAnimation { + BlobsAnimation( + std::vector blobDatas, + float levelDuration, + float maxLevel) + : blobs(std::move(blobDatas), levelDuration, maxLevel) { + } + + Ui::Paint::Blobs blobs; + crl::time lastTime = 0; + crl::time lastSpeakingUpdateTime = 0; + float64 enter = 0.; +}; + +struct GroupCallBar::Userpic { + User data; + std::pair cacheKey; + crl::time speakingStarted = 0; + QImage cache; + Animations::Simple leftAnimation; + Animations::Simple shownAnimation; + std::unique_ptr blobsAnimation; + int left = 0; + bool positionInited = false; + bool topMost = false; + bool hiding = false; + bool cacheMasked = false; +}; + GroupCallBar::GroupCallBar( not_null parent, rpl::producer content) @@ -36,7 +96,8 @@ GroupCallBar::GroupCallBar( _inner.get(), tr::lng_group_call_join(), st::groupCallTopBarJoin)) -, _shadow(std::make_unique(_wrap.parentWidget())) { +, _shadow(std::make_unique(_wrap.parentWidget())) +, _randomSpeakingTimer([=] { sendRandomLevels(); }) { _wrap.hide(anim::type::instant); _shadow->hide(); @@ -78,6 +139,28 @@ GroupCallBar::GroupCallBar( _wrap.toggle(false, anim::type::normal); }, lifetime()); + style::PaletteChanged( + ) | rpl::start_with_next([=] { + for (auto &userpic : _userpics) { + userpic.cache = QImage(); + } + }, lifetime()); + + _speakingAnimation.init([=](crl::time now) { + //if (const auto &last = _speakingAnimationHideLastTime; (last > 0) + // && (now - last >= kBlobsEnterDuration)) { + // _speakingAnimation.stop(); + // return false; + //} + for (auto &userpic : _userpics) { + if (const auto blobs = userpic.blobsAnimation.get()) { + blobs->blobs.updateLevel(now - blobs->lastTime); + blobs->lastTime = now; + } + } + updateUserpics(); + }); + setupInner(); } @@ -168,17 +251,30 @@ void GroupCallBar::paintUserpics(Painter &p) { validateUserpicCache(userpic); p.setOpacity(shown); const auto left = middle + userpic.leftAnimation.value(userpic.left); - if (userpic.data.speaking) { - //p.fillRect(left, top, size, size, QColor(255, 128, 128)); + const auto blobs = userpic.blobsAnimation.get(); + const auto shownScale = 0.5 + shown / 2.; + const auto &minScale = kUserpicMinScale; + const auto scale = shownScale * (blobs + ? (minScale + (1. - minScale) * blobs->blobs.currentLevel()) + : 1.); + if (blobs) { + auto hq = PainterHighQualityEnabler(p); + + const auto shift = QPointF(left + size / 2., top + size / 2.); + p.translate(shift); + blobs->blobs.paint(p, st::windowActiveTextFg); + p.translate(-shift); + p.setOpacity(1.); } - if (shown == 1.) { + if (std::abs(scale - 1.) < 0.001) { const auto skip = ((kWideScale - 1) / 2) * size * factor; p.drawImage( QRect(left, top, size, size), userpic.cache, QRect(skip, skip, size * factor, size * factor)); } else { - const auto scale = 0.5 + shown / 2.; + auto hq = PainterHighQualityEnabler(p); + auto target = QRect( left + (1 - kWideScale) / 2 * size, top + (1 - kWideScale) / 2 * size, @@ -215,6 +311,26 @@ bool GroupCallBar::needUserpicCacheRefresh(Userpic &userpic) { return !userpic.leftAnimation.animating(); } +void GroupCallBar::ensureBlobsAnimation(Userpic &userpic) { + if (userpic.blobsAnimation) { + return; + } + userpic.blobsAnimation = std::make_unique( + Blobs() | ranges::to_vector, + kLevelDuration, + kMaxLevel); + userpic.blobsAnimation->lastTime = crl::now(); +} + +void GroupCallBar::sendRandomLevels() { + for (auto &userpic : _userpics) { + if (const auto blobs = userpic.blobsAnimation.get()) { + const auto value = 30 + (openssl::RandomValue() % 70); + userpic.blobsAnimation->blobs.setLevel(float64(value) / 100.); + } + } +} + void GroupCallBar::validateUserpicCache(Userpic &userpic) { if (!needUserpicCacheRefresh(userpic)) { return; @@ -248,7 +364,6 @@ void GroupCallBar::validateUserpicCache(Userpic &userpic) { } void GroupCallBar::updateControlsGeometry(QRect wrapGeometry) { - _inner->resizeToWidth(wrapGeometry.width()); const auto hidden = _wrap.isHidden() || !wrapGeometry.height(); if (_shadow->isHidden() != hidden) { _shadow->setVisible(!hidden); @@ -308,8 +423,15 @@ void GroupCallBar::updateUserpicsFromContent() { const auto userpicsBegin = begin(_userpics); const auto userpicsEnd = end(_userpics); auto markedTopMost = userpicsEnd; + auto hasBlobs = false; for (auto i = userpicsBegin; i != userpicsEnd; ++i) { auto &userpic = *i; + if (userpic.data.speaking) { + ensureBlobsAnimation(userpic); + hasBlobs = true; + } else { + userpic.blobsAnimation = nullptr; + } if (userpic.topMost) { toggleUserpic(userpic, false); userpic.topMost = false; @@ -323,6 +445,21 @@ void GroupCallBar::updateUserpicsFromContent() { std::rotate(userpicsBegin, markedTopMost, markedTopMost + 1); } updateUserpicsPositions(); + + if (!hasBlobs) { + _randomSpeakingTimer.cancel(); + _speakingAnimation.stop(); + } else if (!_randomSpeakingTimer.isActive()) { + _randomSpeakingTimer.callEach(kSendRandomLevelInterval); + _speakingAnimation.start(); + } + + if (_wrap.isHidden()) { + for (auto &userpic : _userpics) { + userpic.shownAnimation.stop(); + userpic.leftAnimation.stop(); + } + } } void GroupCallBar::toggleUserpic(Userpic &userpic, bool shown) { @@ -408,6 +545,7 @@ void GroupCallBar::move(int x, int y) { void GroupCallBar::resizeToWidth(int width) { _wrap.entity()->resizeToWidth(width); + _inner->resizeToWidth(width); } int GroupCallBar::height() const { diff --git a/Telegram/SourceFiles/ui/chat/group_call_bar.h b/Telegram/SourceFiles/ui/chat/group_call_bar.h index ae013a4469..616babafc6 100644 --- a/Telegram/SourceFiles/ui/chat/group_call_bar.h +++ b/Telegram/SourceFiles/ui/chat/group_call_bar.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/slide_wrap.h" #include "ui/effects/animations.h" #include "base/object_ptr.h" +#include "base/timer.h" class Painter; @@ -57,18 +58,9 @@ public: private: using User = GroupCallBarContent::User; - struct Userpic { - User data; - std::pair cacheKey; - QImage cache; - Animations::Simple leftAnimation; - Animations::Simple shownAnimation; - int left = 0; - bool positionInited = false; - bool topMost = false; - bool hiding = false; - bool cacheMasked = false; - }; + struct BlobsAnimation; + struct Userpic; + void updateShadowGeometry(QRect wrapGeometry); void updateControlsGeometry(QRect wrapGeometry); void updateUserpicsFromContent(); @@ -81,6 +73,8 @@ private: void updateUserpicsPositions(); void validateUserpicCache(Userpic &userpic); [[nodiscard]] bool needUserpicCacheRefresh(Userpic &userpic); + void ensureBlobsAnimation(Userpic &userpic); + void sendRandomLevels(); SlideWrap<> _wrap; not_null _inner; @@ -89,6 +83,8 @@ private: rpl::event_stream<> _barClicks; Fn _shadowGeometryPostprocess; std::vector _userpics; + base::Timer _randomSpeakingTimer; + Ui::Animations::Basic _speakingAnimation; int _maxUserpicsWidth = 0; bool _shouldBeShown = false; bool _forceHidden = false;