Show speaking animations in voice chat bar.

This commit is contained in:
John Preston 2020-12-11 18:53:02 +04:00
parent fe23ba086a
commit 7217d14f09
5 changed files with 158 additions and 20 deletions

View File

@ -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);

View File

@ -141,6 +141,7 @@ rpl::producer<Ui::GroupCallBarContent> 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({

View File

@ -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;

View File

@ -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<Ui::Paint::Blobs::BlobData, 2> {
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<Ui::Paint::Blobs::BlobData> 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<uint64, uint64> cacheKey;
crl::time speakingStarted = 0;
QImage cache;
Animations::Simple leftAnimation;
Animations::Simple shownAnimation;
std::unique_ptr<BlobsAnimation> blobsAnimation;
int left = 0;
bool positionInited = false;
bool topMost = false;
bool hiding = false;
bool cacheMasked = false;
};
GroupCallBar::GroupCallBar(
not_null<QWidget*> parent,
rpl::producer<GroupCallBarContent> content)
@ -36,7 +96,8 @@ GroupCallBar::GroupCallBar(
_inner.get(),
tr::lng_group_call_join(),
st::groupCallTopBarJoin))
, _shadow(std::make_unique<PlainShadow>(_wrap.parentWidget())) {
, _shadow(std::make_unique<PlainShadow>(_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<BlobsAnimation>(
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<uint32>() % 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 {

View File

@ -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<uint64, uint64> 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<RpWidget*> _inner;
@ -89,6 +83,8 @@ private:
rpl::event_stream<> _barClicks;
Fn<QRect(QRect)> _shadowGeometryPostprocess;
std::vector<Userpic> _userpics;
base::Timer _randomSpeakingTimer;
Ui::Animations::Basic _speakingAnimation;
int _maxUserpicsWidth = 0;
bool _shouldBeShown = false;
bool _forceHidden = false;