mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-07 15:43:55 +02:00
Add bouncing of userpics in group call members.
This commit is contained in:
parent
498d6226e3
commit
ef119dadd5
6 changed files with 140 additions and 32 deletions
|
@ -550,8 +550,8 @@ groupCallInactiveButton: IconButton {
|
||||||
rippleAreaSize: 36px;
|
rippleAreaSize: 36px;
|
||||||
}
|
}
|
||||||
groupCallActiveButton: IconButton(groupCallInactiveButton) {
|
groupCallActiveButton: IconButton(groupCallInactiveButton) {
|
||||||
icon: icon {{ "calls/group_calls_unmuted", groupCallMemberActiveIcon }};
|
icon: icon {{ "calls/group_calls_unmuted", groupCallMemberInactiveIcon }};
|
||||||
iconOver: icon {{ "calls/group_calls_unmuted", groupCallMemberActiveIcon }};
|
iconOver: icon {{ "calls/group_calls_unmuted", groupCallMemberInactiveIcon }};
|
||||||
}
|
}
|
||||||
groupCallMutedButton: IconButton(groupCallInactiveButton) {
|
groupCallMutedButton: IconButton(groupCallInactiveButton) {
|
||||||
icon: icon {{ "calls/group_calls_muted", groupCallMemberMutedIcon }};
|
icon: icon {{ "calls/group_calls_muted", groupCallMemberMutedIcon }};
|
||||||
|
@ -661,5 +661,5 @@ groupCallBoxLabel: FlatLabel(boxLabel) {
|
||||||
textFg: groupCallMembersFg;
|
textFg: groupCallMembersFg;
|
||||||
}
|
}
|
||||||
|
|
||||||
groupCallRowBlobMinRadius: 26px;
|
groupCallRowBlobMinRadius: 28px;
|
||||||
groupCallRowBlobMaxRadius: 30px;
|
groupCallRowBlobMaxRadius: 31px;
|
||||||
|
|
|
@ -37,7 +37,6 @@ namespace {
|
||||||
|
|
||||||
constexpr auto kMaxInvitePerSlice = 10;
|
constexpr auto kMaxInvitePerSlice = 10;
|
||||||
constexpr auto kCheckLastSpokeInterval = 3 * crl::time(1000);
|
constexpr auto kCheckLastSpokeInterval = 3 * crl::time(1000);
|
||||||
constexpr auto kSpeakLevelThreshold = 0.2;
|
|
||||||
constexpr auto kCheckJoinedTimeout = 4 * crl::time(1000);
|
constexpr auto kCheckJoinedTimeout = 4 * crl::time(1000);
|
||||||
constexpr auto kUpdateSendActionEach = crl::time(500);
|
constexpr auto kUpdateSendActionEach = crl::time(500);
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,7 @@ public:
|
||||||
[[nodiscard]] rpl::producer<LevelUpdate> levelUpdates() const {
|
[[nodiscard]] rpl::producer<LevelUpdate> levelUpdates() const {
|
||||||
return _levelUpdates.events();
|
return _levelUpdates.events();
|
||||||
}
|
}
|
||||||
|
static constexpr auto kSpeakLevelThreshold = 0.2;
|
||||||
|
|
||||||
void setCurrentAudioDevice(bool input, const QString &deviceId);
|
void setCurrentAudioDevice(bool input, const QString &deviceId);
|
||||||
//void setAudioVolume(bool input, float level);
|
//void setAudioVolume(bool input, float level);
|
||||||
|
|
|
@ -32,25 +32,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace Calls {
|
namespace Calls {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kLevelThreshold = 0.2;
|
constexpr auto kBlobsEnterDuration = crl::time(250);
|
||||||
constexpr auto kRowBlobRadiusFactor = (float)(50. / 57.);
|
constexpr auto kRowBlobRadiusFactor = (float)(50. / 57.);
|
||||||
constexpr auto kLevelDuration = 100. + 500. * 0.33;
|
constexpr auto kLevelDuration = 100. + 500. * 0.33;
|
||||||
constexpr auto kScaleSmall = 0.704 - 0.1;
|
constexpr auto kScaleSmall = 0.704 - 0.1;
|
||||||
constexpr auto kScaleSmallMin = 0.926;
|
constexpr auto kScaleSmallMin = 0.926;
|
||||||
constexpr auto kScaleSmallMax = (float)(kScaleSmallMin + kScaleSmall);
|
constexpr auto kScaleSmallMax = (float)(kScaleSmallMin + kScaleSmall);
|
||||||
constexpr auto kMaxLevel = 1.;
|
constexpr auto kMaxLevel = 1.;
|
||||||
|
constexpr auto kWideScale = 5;
|
||||||
|
|
||||||
auto RowBlobs() -> std::array<Ui::Paint::Blobs::BlobData, 2> {
|
auto RowBlobs() -> std::array<Ui::Paint::Blobs::BlobData, 2> {
|
||||||
return { {
|
return { {
|
||||||
{
|
{
|
||||||
.segmentsCount = 6,
|
.segmentsCount = 6,
|
||||||
.minScale = kScaleSmallMin / kScaleSmallMax,
|
.minScale = (kScaleSmallMin / kScaleSmallMax) * 0.9f,
|
||||||
.minRadius = st::groupCallRowBlobMinRadius
|
.minRadius = st::groupCallRowBlobMinRadius
|
||||||
* kRowBlobRadiusFactor,
|
* kRowBlobRadiusFactor * 0.9f,
|
||||||
.maxRadius = st::groupCallRowBlobMaxRadius
|
.maxRadius = st::groupCallRowBlobMaxRadius
|
||||||
* kRowBlobRadiusFactor,
|
* kRowBlobRadiusFactor * 0.9f,
|
||||||
.speedScale = 1.,
|
.speedScale = 1.,
|
||||||
.alpha = (76. / 255.),
|
.alpha = .5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.segmentsCount = 8,
|
.segmentsCount = 8,
|
||||||
|
@ -60,7 +61,7 @@ auto RowBlobs() -> std::array<Ui::Paint::Blobs::BlobData, 2> {
|
||||||
.maxRadius = st::groupCallRowBlobMaxRadius
|
.maxRadius = st::groupCallRowBlobMaxRadius
|
||||||
* kRowBlobRadiusFactor,
|
* kRowBlobRadiusFactor,
|
||||||
.speedScale = 1.,
|
.speedScale = 1.,
|
||||||
.alpha = (76. / 255.),
|
.alpha = .2,
|
||||||
},
|
},
|
||||||
} };
|
} };
|
||||||
}
|
}
|
||||||
|
@ -118,6 +119,28 @@ public:
|
||||||
auto generatePaintUserpicCallback() -> PaintRoundImageCallback override;
|
auto generatePaintUserpicCallback() -> PaintRoundImageCallback override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct SpeakingAnimation {
|
||||||
|
SpeakingAnimation(
|
||||||
|
std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
|
||||||
|
float levelDuration,
|
||||||
|
float maxLevel)
|
||||||
|
: blobs(std::move(blobDatas), levelDuration, maxLevel) {
|
||||||
|
style::PaletteChanged(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
userpicCache = QImage();
|
||||||
|
}, lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ui::Paint::Blobs blobs;
|
||||||
|
crl::time lastTime = 0;
|
||||||
|
crl::time lastSpeakingUpdateTime = 0;
|
||||||
|
float64 enter = 0.;
|
||||||
|
|
||||||
|
QImage userpicCache;
|
||||||
|
InMemoryKey userpicKey;
|
||||||
|
|
||||||
|
rpl::lifetime lifetime;
|
||||||
|
};
|
||||||
void refreshStatus() override;
|
void refreshStatus() override;
|
||||||
void setSpeaking(bool speaking);
|
void setSpeaking(bool speaking);
|
||||||
void setSsrc(uint32 ssrc);
|
void setSsrc(uint32 ssrc);
|
||||||
|
@ -128,12 +151,15 @@ private:
|
||||||
[[nodiscard]] static not_null<const style::IconButton*> ComputeIconStyle(
|
[[nodiscard]] static not_null<const style::IconButton*> ComputeIconStyle(
|
||||||
State state);
|
State state);
|
||||||
|
|
||||||
|
void ensureUserpicCache(
|
||||||
|
std::shared_ptr<Data::CloudImageView> &view,
|
||||||
|
int size);
|
||||||
|
|
||||||
State _state = State::Inactive;
|
State _state = State::Inactive;
|
||||||
not_null<ChannelData*> _channel;
|
not_null<ChannelData*> _channel;
|
||||||
not_null<const style::IconButton*> _st;
|
not_null<const style::IconButton*> _st;
|
||||||
std::unique_ptr<Ui::RippleAnimation> _actionRipple;
|
std::unique_ptr<Ui::RippleAnimation> _actionRipple;
|
||||||
std::unique_ptr<Ui::Paint::Blobs> _blobs;
|
std::unique_ptr<SpeakingAnimation> _speakingAnimation;
|
||||||
crl::time _blobsLastTime = 0;
|
|
||||||
uint32 _ssrc = 0;
|
uint32 _ssrc = 0;
|
||||||
float _level = 0.;
|
float _level = 0.;
|
||||||
bool _speaking = false;
|
bool _speaking = false;
|
||||||
|
@ -247,7 +273,14 @@ void Row::setSpeaking(bool speaking) {
|
||||||
}
|
}
|
||||||
_speaking = speaking;
|
_speaking = speaking;
|
||||||
if (!_speaking) {
|
if (!_speaking) {
|
||||||
_blobs = nullptr;
|
_speakingAnimation = nullptr;
|
||||||
|
} else if (!_speakingAnimation) {
|
||||||
|
_speakingAnimation = std::make_unique<SpeakingAnimation>(
|
||||||
|
RowBlobs() | ranges::to_vector,
|
||||||
|
kLevelDuration,
|
||||||
|
kMaxLevel);
|
||||||
|
_speakingAnimation->lastTime = crl::now();
|
||||||
|
updateLevel(GroupCall::kSpeakLevelThreshold);
|
||||||
}
|
}
|
||||||
refreshStatus();
|
refreshStatus();
|
||||||
}
|
}
|
||||||
|
@ -257,36 +290,102 @@ void Row::setSsrc(uint32 ssrc) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Row::updateLevel(float level) {
|
void Row::updateLevel(float level) {
|
||||||
Expects(_speaking);
|
Expects(_speakingAnimation != nullptr);
|
||||||
|
|
||||||
if (!_blobs) {
|
if (level >= GroupCall::kSpeakLevelThreshold) {
|
||||||
_blobs = std::make_unique<Ui::Paint::Blobs>(
|
_speakingAnimation->lastSpeakingUpdateTime = crl::now();
|
||||||
RowBlobs() | ranges::to_vector,
|
|
||||||
kLevelDuration,
|
|
||||||
kMaxLevel);
|
|
||||||
_blobsLastTime = crl::now();
|
|
||||||
}
|
}
|
||||||
_blobs->setLevel(level + 0.5);
|
_speakingAnimation->blobs.setLevel(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Row::updateBlobAnimation(crl::time now) {
|
void Row::updateBlobAnimation(crl::time now) {
|
||||||
if (_blobs) {
|
Expects(_speakingAnimation != nullptr);
|
||||||
_blobs->updateLevel(now - _blobsLastTime);
|
|
||||||
_blobsLastTime = now;
|
const auto speakingFinishesAt = _speakingAnimation->lastSpeakingUpdateTime
|
||||||
|
+ Data::GroupCall::kSpeakStatusKeptFor;
|
||||||
|
const auto speakingStartsFinishing = speakingFinishesAt
|
||||||
|
- kBlobsEnterDuration;
|
||||||
|
const auto speakingFinishes = (speakingStartsFinishing < now);
|
||||||
|
if (speakingFinishes) {
|
||||||
|
_speakingAnimation->enter = std::clamp(
|
||||||
|
(speakingFinishesAt - now) / float64(kBlobsEnterDuration),
|
||||||
|
0.,
|
||||||
|
1.);
|
||||||
|
} else if (_speakingAnimation->enter < 1.) {
|
||||||
|
_speakingAnimation->enter = std::clamp(
|
||||||
|
(_speakingAnimation->enter
|
||||||
|
+ ((now - _speakingAnimation->lastTime)
|
||||||
|
/ float64(kBlobsEnterDuration))),
|
||||||
|
0.,
|
||||||
|
1.);
|
||||||
|
}
|
||||||
|
_speakingAnimation->blobs.updateLevel(now - _speakingAnimation->lastTime);
|
||||||
|
_speakingAnimation->lastTime = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Row::ensureUserpicCache(
|
||||||
|
std::shared_ptr<Data::CloudImageView> &view,
|
||||||
|
int size) {
|
||||||
|
Expects(_speakingAnimation != nullptr);
|
||||||
|
|
||||||
|
const auto user = peer();
|
||||||
|
const auto key = user->userpicUniqueKey(view);
|
||||||
|
const auto full = QSize(size, size) * kWideScale * cIntRetinaFactor();
|
||||||
|
auto &cache = _speakingAnimation->userpicCache;
|
||||||
|
if (cache.isNull()) {
|
||||||
|
cache = QImage(full, QImage::Format_ARGB32_Premultiplied);
|
||||||
|
cache.setDevicePixelRatio(cRetinaFactor());
|
||||||
|
} else if (_speakingAnimation->userpicKey == key
|
||||||
|
&& cache.size() == full) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_speakingAnimation->userpicKey = key;
|
||||||
|
cache.fill(Qt::transparent);
|
||||||
|
{
|
||||||
|
Painter p(&cache);
|
||||||
|
const auto skip = (kWideScale - 1) / 2 * size;
|
||||||
|
user->paintUserpicLeft(p, view, skip, skip, kWideScale * size, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Row::generatePaintUserpicCallback() -> PaintRoundImageCallback {
|
auto Row::generatePaintUserpicCallback() -> PaintRoundImageCallback {
|
||||||
auto userpic = ensureUserpicView();
|
auto userpic = ensureUserpicView();
|
||||||
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
||||||
if (_blobs) {
|
if (_speakingAnimation) {
|
||||||
const auto shift = QPointF(x + size / 2., y + size / 2.);
|
const auto shift = QPointF(x + size / 2., y + size / 2.);
|
||||||
p.translate(shift);
|
p.translate(shift);
|
||||||
_blobs->paint(p, st::groupCallLive1);
|
_speakingAnimation->blobs.paint(p, st::groupCallMemberActiveStatus);
|
||||||
p.translate(-shift);
|
p.translate(-shift);
|
||||||
p.setOpacity(1.);
|
p.setOpacity(1.);
|
||||||
|
|
||||||
|
const auto enter = _speakingAnimation->enter;
|
||||||
|
const auto scaleAvatar = 0.8
|
||||||
|
+ 0.2 * _speakingAnimation->blobs.currentLevel();
|
||||||
|
const auto scale = scaleAvatar * enter + 1. * (1. - enter);
|
||||||
|
if (scale == 1.) {
|
||||||
|
peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||||
|
} else {
|
||||||
|
ensureUserpicCache(userpic, size);
|
||||||
|
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
|
||||||
|
auto target = QRect(
|
||||||
|
x + (1 - kWideScale) / 2 * size,
|
||||||
|
y + (1 - kWideScale) / 2 * size,
|
||||||
|
kWideScale * size,
|
||||||
|
kWideScale * size);
|
||||||
|
auto shrink = anim::interpolate(
|
||||||
|
(1 - kWideScale) / 2 * size,
|
||||||
|
0,
|
||||||
|
scale);
|
||||||
|
auto margins = QMargins(shrink, shrink, shrink, shrink);
|
||||||
|
p.drawImage(
|
||||||
|
target.marginsAdded(margins),
|
||||||
|
_speakingAnimation->userpicCache);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||||
}
|
}
|
||||||
peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,9 +407,17 @@ void Row::paintAction(
|
||||||
_actionRipple.reset();
|
_actionRipple.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_st->icon.paintInCenter(
|
const auto iconRect = style::rtlrect(
|
||||||
p,
|
x,
|
||||||
style::rtlrect(x, y, size.width(), size.height(), outerWidth));
|
y,
|
||||||
|
size.width(),
|
||||||
|
size.height(),
|
||||||
|
outerWidth);
|
||||||
|
if (_speaking) {
|
||||||
|
_st->icon.paintInCenter(p, iconRect, st::groupCallMemberActiveIcon->c);
|
||||||
|
} else {
|
||||||
|
_st->icon.paintInCenter(p, iconRect);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Row::refreshStatus() {
|
void Row::refreshStatus() {
|
||||||
|
|
|
@ -17,7 +17,6 @@ namespace Data {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kRequestPerPage = 30;
|
constexpr auto kRequestPerPage = 30;
|
||||||
constexpr auto kSpeakStatusKeptFor = crl::time(1000);
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,8 @@ public:
|
||||||
std::optional<Participant> now;
|
std::optional<Participant> now;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static constexpr auto kSpeakStatusKeptFor = crl::time(1000);
|
||||||
|
|
||||||
[[nodiscard]] auto participants() const
|
[[nodiscard]] auto participants() const
|
||||||
-> const std::vector<Participant> &;
|
-> const std::vector<Participant> &;
|
||||||
void requestParticipants();
|
void requestParticipants();
|
||||||
|
|
Loading…
Add table
Reference in a new issue