mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Add animations to group call member mute status.
This commit is contained in:
parent
fdbe6bdeb2
commit
101409d866
4 changed files with 206 additions and 108 deletions
|
@ -537,28 +537,32 @@ groupCallHeaderLabel: FlatLabel(defaultFlatLabel) {
|
||||||
groupCallAddButtonPosition: point(10px, 7px);
|
groupCallAddButtonPosition: point(10px, 7px);
|
||||||
groupCallMembersWidthMax: 360px;
|
groupCallMembersWidthMax: 360px;
|
||||||
|
|
||||||
groupCallInactiveButton: IconButton {
|
groupCallActiveButton: IconButton {
|
||||||
width: 36px;
|
width: 36px;
|
||||||
height: 52px;
|
height: 52px;
|
||||||
|
|
||||||
icon: icon {{ "calls/group_calls_muted", groupCallMemberInactiveIcon }};
|
icon: icon {{ "calls/group_calls_unmuted", groupCallMemberInactiveIcon }};
|
||||||
iconOver: icon {{ "calls/group_calls_muted", groupCallMemberInactiveIcon }};
|
iconOver: icon {{ "calls/group_calls_unmuted", groupCallMemberInactiveIcon }};
|
||||||
iconPosition: point(-1px, -1px);
|
iconPosition: point(-1px, -1px);
|
||||||
|
|
||||||
ripple: groupCallRipple;
|
ripple: groupCallRipple;
|
||||||
rippleAreaPosition: point(0px, 8px);
|
rippleAreaPosition: point(0px, 8px);
|
||||||
rippleAreaSize: 36px;
|
rippleAreaSize: 36px;
|
||||||
}
|
}
|
||||||
groupCallActiveButton: IconButton(groupCallInactiveButton) {
|
|
||||||
icon: icon {{ "calls/group_calls_unmuted", groupCallMemberInactiveIcon }};
|
|
||||||
iconOver: icon {{ "calls/group_calls_unmuted", groupCallMemberInactiveIcon }};
|
|
||||||
}
|
|
||||||
groupCallMutedButton: IconButton(groupCallInactiveButton) {
|
|
||||||
icon: icon {{ "calls/group_calls_muted", groupCallMemberMutedIcon }};
|
|
||||||
iconOver: icon {{ "calls/group_calls_muted", groupCallMemberMutedIcon }};
|
|
||||||
}
|
|
||||||
groupCallMemberButtonSkip: 10px;
|
groupCallMemberButtonSkip: 10px;
|
||||||
|
|
||||||
|
groupCallMemberInactiveCrossLine: CrossLineAnimation {
|
||||||
|
fg: groupCallMemberInactiveIcon;
|
||||||
|
icon: icon {{ "calls/group_calls_unmuted", groupCallMemberInactiveIcon }};
|
||||||
|
startPosition: point(5px, 2px);
|
||||||
|
endPosition: point(20px, 17px);
|
||||||
|
stroke: 2px;
|
||||||
|
}
|
||||||
|
groupCallMemberColoredCrossLine: CrossLineAnimation(groupCallMemberInactiveCrossLine) {
|
||||||
|
fg: groupCallMemberMutedIcon;
|
||||||
|
icon: icon {{ "calls/group_calls_unmuted", groupCallMemberActiveIcon }};
|
||||||
|
}
|
||||||
|
|
||||||
groupCallSettings: CallButton(callMicrophoneMute) {
|
groupCallSettings: CallButton(callMicrophoneMute) {
|
||||||
button: IconButton(callButton) {
|
button: IconButton(callButton) {
|
||||||
iconPosition: point(-1px, 22px);
|
iconPosition: point(-1px, 22px);
|
||||||
|
@ -661,8 +665,8 @@ groupCallBoxLabel: FlatLabel(boxLabel) {
|
||||||
textFg: groupCallMembersFg;
|
textFg: groupCallMembersFg;
|
||||||
}
|
}
|
||||||
|
|
||||||
groupCallRowBlobMinRadius: 28px;
|
groupCallRowBlobMinRadius: 27px;
|
||||||
groupCallRowBlobMaxRadius: 30px;
|
groupCallRowBlobMaxRadius: 29px;
|
||||||
|
|
||||||
callTopBarMuteCrossLine: CrossLineAnimation {
|
callTopBarMuteCrossLine: CrossLineAnimation {
|
||||||
fg: callBarFg;
|
fg: callBarFg;
|
||||||
|
|
|
@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
|
#include "ui/effects/cross_line.h"
|
||||||
#include "core/application.h" // Core::App().domain, Core::App().activeWindow.
|
#include "core/application.h" // Core::App().domain, Core::App().activeWindow.
|
||||||
#include "main/main_domain.h" // Core::App().domain().activate.
|
#include "main/main_domain.h" // Core::App().domain().activate.
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
@ -61,9 +62,23 @@ auto RowBlobs() -> std::array<Ui::Paint::Blobs::BlobData, 2> {
|
||||||
} };
|
} };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Row;
|
||||||
|
|
||||||
|
class RowDelegate {
|
||||||
|
public:
|
||||||
|
virtual bool rowCanMuteMembers() = 0;
|
||||||
|
virtual void rowUpdateRow(not_null<Row*> row) = 0;
|
||||||
|
virtual void rowPaintIcon(
|
||||||
|
Painter &p,
|
||||||
|
QRect rect,
|
||||||
|
float64 speaking,
|
||||||
|
float64 active,
|
||||||
|
float64 muted) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
class Row final : public PeerListRow {
|
class Row final : public PeerListRow {
|
||||||
public:
|
public:
|
||||||
Row(not_null<ChannelData*> channel, not_null<UserData*> user);
|
Row(not_null<RowDelegate*> delegate, not_null<UserData*> user);
|
||||||
|
|
||||||
enum class State {
|
enum class State {
|
||||||
Active,
|
Active,
|
||||||
|
@ -91,10 +106,12 @@ public:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
QSize actionSize() const override {
|
QSize actionSize() const override {
|
||||||
return QSize(_st->width, _st->height);
|
return QSize(
|
||||||
|
st::groupCallActiveButton.width,
|
||||||
|
st::groupCallActiveButton.height);
|
||||||
}
|
}
|
||||||
bool actionDisabled() const override {
|
bool actionDisabled() const override {
|
||||||
return peer()->isSelf() || !_channel->canManageCall();
|
return peer()->isSelf() || !_delegate->rowCanMuteMembers();
|
||||||
}
|
}
|
||||||
QMargins actionMargins() const override {
|
QMargins actionMargins() const override {
|
||||||
return QMargins(
|
return QMargins(
|
||||||
|
@ -114,8 +131,8 @@ public:
|
||||||
auto generatePaintUserpicCallback() -> PaintRoundImageCallback override;
|
auto generatePaintUserpicCallback() -> PaintRoundImageCallback override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct SpeakingAnimation {
|
struct BlobsAnimation {
|
||||||
SpeakingAnimation(
|
BlobsAnimation(
|
||||||
std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
|
std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
|
||||||
float levelDuration,
|
float levelDuration,
|
||||||
float maxLevel)
|
float maxLevel)
|
||||||
|
@ -138,23 +155,20 @@ private:
|
||||||
};
|
};
|
||||||
void refreshStatus() override;
|
void refreshStatus() override;
|
||||||
void setSpeaking(bool speaking);
|
void setSpeaking(bool speaking);
|
||||||
|
void setState(State state);
|
||||||
void setSsrc(uint32 ssrc);
|
void setSsrc(uint32 ssrc);
|
||||||
|
|
||||||
[[nodiscard]] static State ComputeState(
|
|
||||||
not_null<ChannelData*> channel,
|
|
||||||
not_null<UserData*> user);
|
|
||||||
[[nodiscard]] static not_null<const style::IconButton*> ComputeIconStyle(
|
|
||||||
State state);
|
|
||||||
|
|
||||||
void ensureUserpicCache(
|
void ensureUserpicCache(
|
||||||
std::shared_ptr<Data::CloudImageView> &view,
|
std::shared_ptr<Data::CloudImageView> &view,
|
||||||
int size);
|
int size);
|
||||||
|
|
||||||
|
const not_null<RowDelegate*> _delegate;
|
||||||
State _state = State::Inactive;
|
State _state = State::Inactive;
|
||||||
not_null<ChannelData*> _channel;
|
|
||||||
not_null<const style::IconButton*> _st;
|
|
||||||
std::unique_ptr<Ui::RippleAnimation> _actionRipple;
|
std::unique_ptr<Ui::RippleAnimation> _actionRipple;
|
||||||
std::unique_ptr<SpeakingAnimation> _speakingAnimation;
|
std::unique_ptr<BlobsAnimation> _blobsAnimation;
|
||||||
|
Ui::Animations::Simple _speakingAnimation; // For gray-red/green icon.
|
||||||
|
Ui::Animations::Simple _mutedAnimation; // For gray/red icon.
|
||||||
|
Ui::Animations::Simple _activeAnimation; // For icon cross animation.
|
||||||
uint32 _ssrc = 0;
|
uint32 _ssrc = 0;
|
||||||
bool _speaking = false;
|
bool _speaking = false;
|
||||||
|
|
||||||
|
@ -162,6 +176,7 @@ private:
|
||||||
|
|
||||||
class MembersController final
|
class MembersController final
|
||||||
: public PeerListController
|
: public PeerListController
|
||||||
|
, public RowDelegate
|
||||||
, public base::has_weak_ptr {
|
, public base::has_weak_ptr {
|
||||||
public:
|
public:
|
||||||
MembersController(
|
MembersController(
|
||||||
|
@ -186,6 +201,15 @@ public:
|
||||||
[[nodiscard]] auto kickMemberRequests() const
|
[[nodiscard]] auto kickMemberRequests() const
|
||||||
-> rpl::producer<not_null<UserData*>>;
|
-> rpl::producer<not_null<UserData*>>;
|
||||||
|
|
||||||
|
bool rowCanMuteMembers() override;
|
||||||
|
void rowUpdateRow(not_null<Row*> row) override;
|
||||||
|
void rowPaintIcon(
|
||||||
|
Painter &p,
|
||||||
|
QRect rect,
|
||||||
|
float64 speaking,
|
||||||
|
float64 active,
|
||||||
|
float64 muted) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] std::unique_ptr<Row> createSelfRow();
|
[[nodiscard]] std::unique_ptr<Row> createSelfRow();
|
||||||
[[nodiscard]] std::unique_ptr<Row> createRow(
|
[[nodiscard]] std::unique_ptr<Row> createRow(
|
||||||
|
@ -226,15 +250,16 @@ private:
|
||||||
base::flat_map<uint32, not_null<Row*>> _speakingRowBySsrc;
|
base::flat_map<uint32, not_null<Row*>> _speakingRowBySsrc;
|
||||||
Ui::Animations::Basic _speakingAnimation;
|
Ui::Animations::Basic _speakingAnimation;
|
||||||
|
|
||||||
|
Ui::CrossLineAnimation _inactiveCrossLine;
|
||||||
|
Ui::CrossLineAnimation _coloredCrossLine;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Row::Row(not_null<ChannelData*> channel, not_null<UserData*> user)
|
Row::Row(not_null<RowDelegate*> delegate, not_null<UserData*> user)
|
||||||
: PeerListRow(user)
|
: PeerListRow(user)
|
||||||
, _state(ComputeState(channel, user))
|
, _delegate(delegate) {
|
||||||
, _channel(channel)
|
|
||||||
, _st(ComputeIconStyle(_state)) {
|
|
||||||
refreshStatus();
|
refreshStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,19 +271,18 @@ void Row::updateState(const Data::GroupCall::Participant *participant) {
|
||||||
} else {
|
} else {
|
||||||
setCustomStatus(QString());
|
setCustomStatus(QString());
|
||||||
}
|
}
|
||||||
_state = State::Inactive;
|
setState(State::Inactive);
|
||||||
setSpeaking(false);
|
setSpeaking(false);
|
||||||
} else if (!participant->muted) {
|
} else if (!participant->muted) {
|
||||||
_state = State::Active;
|
setState(State::Active);
|
||||||
setSpeaking(participant->speaking && participant->ssrc != 0);
|
setSpeaking(participant->speaking && participant->ssrc != 0);
|
||||||
} else if (participant->canSelfUnmute) {
|
} else if (participant->canSelfUnmute) {
|
||||||
_state = State::Inactive;
|
setState(State::Inactive);
|
||||||
setSpeaking(false);
|
setSpeaking(false);
|
||||||
} else {
|
} else {
|
||||||
_state = State::Muted;
|
setState(State::Muted);
|
||||||
setSpeaking(false);
|
setSpeaking(false);
|
||||||
}
|
}
|
||||||
_st = ComputeIconStyle(_state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Row::setSpeaking(bool speaking) {
|
void Row::setSpeaking(bool speaking) {
|
||||||
|
@ -266,74 +290,104 @@ void Row::setSpeaking(bool speaking) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_speaking = speaking;
|
_speaking = speaking;
|
||||||
|
_speakingAnimation.start(
|
||||||
|
[=] { _delegate->rowUpdateRow(this); },
|
||||||
|
_speaking ? 0. : 1.,
|
||||||
|
_speaking ? 1. : 0.,
|
||||||
|
st::widgetFadeDuration);
|
||||||
if (!_speaking) {
|
if (!_speaking) {
|
||||||
_speakingAnimation = nullptr;
|
_blobsAnimation = nullptr;
|
||||||
} else if (!_speakingAnimation) {
|
} else if (!_blobsAnimation) {
|
||||||
_speakingAnimation = std::make_unique<SpeakingAnimation>(
|
_blobsAnimation = std::make_unique<BlobsAnimation>(
|
||||||
RowBlobs() | ranges::to_vector,
|
RowBlobs() | ranges::to_vector,
|
||||||
kLevelDuration,
|
kLevelDuration,
|
||||||
kMaxLevel);
|
kMaxLevel);
|
||||||
_speakingAnimation->lastTime = crl::now();
|
_blobsAnimation->lastTime = crl::now();
|
||||||
updateLevel(GroupCall::kSpeakLevelThreshold);
|
updateLevel(GroupCall::kSpeakLevelThreshold);
|
||||||
}
|
}
|
||||||
refreshStatus();
|
refreshStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Row::setState(State state) {
|
||||||
|
if (_state == state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto wasActive = (_state == State::Active);
|
||||||
|
const auto wasMuted = (_state == State::Muted);
|
||||||
|
_state = state;
|
||||||
|
const auto nowActive = (_state == State::Active);
|
||||||
|
const auto nowMuted = (_state == State::Muted);
|
||||||
|
if (nowActive != wasActive) {
|
||||||
|
_activeAnimation.start(
|
||||||
|
[=] { _delegate->rowUpdateRow(this); },
|
||||||
|
nowActive ? 0. : 1.,
|
||||||
|
nowActive ? 1. : 0.,
|
||||||
|
st::widgetFadeDuration);
|
||||||
|
}
|
||||||
|
if (nowMuted != wasMuted) {
|
||||||
|
_mutedAnimation.start(
|
||||||
|
[=] { _delegate->rowUpdateRow(this); },
|
||||||
|
nowMuted ? 0. : 1.,
|
||||||
|
nowMuted ? 1. : 0.,
|
||||||
|
st::widgetFadeDuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Row::setSsrc(uint32 ssrc) {
|
void Row::setSsrc(uint32 ssrc) {
|
||||||
_ssrc = ssrc;
|
_ssrc = ssrc;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Row::updateLevel(float level) {
|
void Row::updateLevel(float level) {
|
||||||
Expects(_speakingAnimation != nullptr);
|
Expects(_blobsAnimation != nullptr);
|
||||||
|
|
||||||
if (level >= GroupCall::kSpeakLevelThreshold) {
|
if (level >= GroupCall::kSpeakLevelThreshold) {
|
||||||
_speakingAnimation->lastSpeakingUpdateTime = crl::now();
|
_blobsAnimation->lastSpeakingUpdateTime = crl::now();
|
||||||
}
|
}
|
||||||
_speakingAnimation->blobs.setLevel(level);
|
_blobsAnimation->blobs.setLevel(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Row::updateBlobAnimation(crl::time now) {
|
void Row::updateBlobAnimation(crl::time now) {
|
||||||
Expects(_speakingAnimation != nullptr);
|
Expects(_blobsAnimation != nullptr);
|
||||||
|
|
||||||
const auto speakingFinishesAt = _speakingAnimation->lastSpeakingUpdateTime
|
const auto speakingFinishesAt = _blobsAnimation->lastSpeakingUpdateTime
|
||||||
+ Data::GroupCall::kSpeakStatusKeptFor;
|
+ Data::GroupCall::kSpeakStatusKeptFor;
|
||||||
const auto speakingStartsFinishing = speakingFinishesAt
|
const auto speakingStartsFinishing = speakingFinishesAt
|
||||||
- kBlobsEnterDuration;
|
- kBlobsEnterDuration;
|
||||||
const auto speakingFinishes = (speakingStartsFinishing < now);
|
const auto speakingFinishes = (speakingStartsFinishing < now);
|
||||||
if (speakingFinishes) {
|
if (speakingFinishes) {
|
||||||
_speakingAnimation->enter = std::clamp(
|
_blobsAnimation->enter = std::clamp(
|
||||||
(speakingFinishesAt - now) / float64(kBlobsEnterDuration),
|
(speakingFinishesAt - now) / float64(kBlobsEnterDuration),
|
||||||
0.,
|
0.,
|
||||||
1.);
|
1.);
|
||||||
} else if (_speakingAnimation->enter < 1.) {
|
} else if (_blobsAnimation->enter < 1.) {
|
||||||
_speakingAnimation->enter = std::clamp(
|
_blobsAnimation->enter = std::clamp(
|
||||||
(_speakingAnimation->enter
|
(_blobsAnimation->enter
|
||||||
+ ((now - _speakingAnimation->lastTime)
|
+ ((now - _blobsAnimation->lastTime)
|
||||||
/ float64(kBlobsEnterDuration))),
|
/ float64(kBlobsEnterDuration))),
|
||||||
0.,
|
0.,
|
||||||
1.);
|
1.);
|
||||||
}
|
}
|
||||||
_speakingAnimation->blobs.updateLevel(now - _speakingAnimation->lastTime);
|
_blobsAnimation->blobs.updateLevel(now - _blobsAnimation->lastTime);
|
||||||
_speakingAnimation->lastTime = now;
|
_blobsAnimation->lastTime = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Row::ensureUserpicCache(
|
void Row::ensureUserpicCache(
|
||||||
std::shared_ptr<Data::CloudImageView> &view,
|
std::shared_ptr<Data::CloudImageView> &view,
|
||||||
int size) {
|
int size) {
|
||||||
Expects(_speakingAnimation != nullptr);
|
Expects(_blobsAnimation != nullptr);
|
||||||
|
|
||||||
const auto user = peer();
|
const auto user = peer();
|
||||||
const auto key = user->userpicUniqueKey(view);
|
const auto key = user->userpicUniqueKey(view);
|
||||||
const auto full = QSize(size, size) * kWideScale * cIntRetinaFactor();
|
const auto full = QSize(size, size) * kWideScale * cIntRetinaFactor();
|
||||||
auto &cache = _speakingAnimation->userpicCache;
|
auto &cache = _blobsAnimation->userpicCache;
|
||||||
if (cache.isNull()) {
|
if (cache.isNull()) {
|
||||||
cache = QImage(full, QImage::Format_ARGB32_Premultiplied);
|
cache = QImage(full, QImage::Format_ARGB32_Premultiplied);
|
||||||
cache.setDevicePixelRatio(cRetinaFactor());
|
cache.setDevicePixelRatio(cRetinaFactor());
|
||||||
} else if (_speakingAnimation->userpicKey == key
|
} else if (_blobsAnimation->userpicKey == key
|
||||||
&& cache.size() == full) {
|
&& cache.size() == full) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_speakingAnimation->userpicKey = key;
|
_blobsAnimation->userpicKey = key;
|
||||||
cache.fill(Qt::transparent);
|
cache.fill(Qt::transparent);
|
||||||
{
|
{
|
||||||
Painter p(&cache);
|
Painter p(&cache);
|
||||||
|
@ -345,17 +399,17 @@ void Row::ensureUserpicCache(
|
||||||
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 (_speakingAnimation) {
|
if (_blobsAnimation) {
|
||||||
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);
|
||||||
_speakingAnimation->blobs.paint(p, st::groupCallMemberActiveStatus);
|
_blobsAnimation->blobs.paint(p, st::groupCallMemberActiveStatus);
|
||||||
p.translate(-shift);
|
p.translate(-shift);
|
||||||
p.setOpacity(1.);
|
p.setOpacity(1.);
|
||||||
|
|
||||||
const auto enter = _speakingAnimation->enter;
|
const auto enter = _blobsAnimation->enter;
|
||||||
const auto &minScale = kUserpicMinScale;
|
const auto &minScale = kUserpicMinScale;
|
||||||
const auto scaleUserpic = minScale
|
const auto scaleUserpic = minScale
|
||||||
+ (1. - minScale) * _speakingAnimation->blobs.currentLevel();
|
+ (1. - minScale) * _blobsAnimation->blobs.currentLevel();
|
||||||
const auto scale = scaleUserpic * enter + 1. * (1. - enter);
|
const auto scale = scaleUserpic * enter + 1. * (1. - enter);
|
||||||
if (scale == 1.) {
|
if (scale == 1.) {
|
||||||
peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||||
|
@ -376,7 +430,7 @@ auto Row::generatePaintUserpicCallback() -> PaintRoundImageCallback {
|
||||||
auto margins = QMargins(shrink, shrink, shrink, shrink);
|
auto margins = QMargins(shrink, shrink, shrink, shrink);
|
||||||
p.drawImage(
|
p.drawImage(
|
||||||
target.marginsAdded(margins),
|
target.marginsAdded(margins),
|
||||||
_speakingAnimation->userpicCache);
|
_blobsAnimation->userpicCache);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||||
|
@ -395,8 +449,8 @@ void Row::paintAction(
|
||||||
if (_actionRipple) {
|
if (_actionRipple) {
|
||||||
_actionRipple->paint(
|
_actionRipple->paint(
|
||||||
p,
|
p,
|
||||||
x + _st->rippleAreaPosition.x(),
|
x + st::groupCallActiveButton.rippleAreaPosition.x(),
|
||||||
y + _st->rippleAreaPosition.y(),
|
y + st::groupCallActiveButton.rippleAreaPosition.y(),
|
||||||
outerWidth);
|
outerWidth);
|
||||||
if (_actionRipple->empty()) {
|
if (_actionRipple->empty()) {
|
||||||
_actionRipple.reset();
|
_actionRipple.reset();
|
||||||
|
@ -408,11 +462,12 @@ void Row::paintAction(
|
||||||
size.width(),
|
size.width(),
|
||||||
size.height(),
|
size.height(),
|
||||||
outerWidth);
|
outerWidth);
|
||||||
if (_speaking) {
|
const auto speaking = _speakingAnimation.value(_speaking ? 1. : 0.);
|
||||||
_st->icon.paintInCenter(p, iconRect, st::groupCallMemberActiveIcon->c);
|
const auto active = _activeAnimation.value(
|
||||||
} else {
|
(_state == State::Active) ? 1. : 0.);
|
||||||
_st->icon.paintInCenter(p, iconRect);
|
const auto muted = _mutedAnimation.value(
|
||||||
}
|
(_state == State::Muted) ? 1. : 0.);
|
||||||
|
_delegate->rowPaintIcon(p, iconRect, speaking, active, muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Row::refreshStatus() {
|
void Row::refreshStatus() {
|
||||||
|
@ -423,48 +478,17 @@ void Row::refreshStatus() {
|
||||||
_speaking);
|
_speaking);
|
||||||
}
|
}
|
||||||
|
|
||||||
Row::State Row::ComputeState(
|
|
||||||
not_null<ChannelData*> channel,
|
|
||||||
not_null<UserData*> user) {
|
|
||||||
const auto call = channel->call();
|
|
||||||
if (!call) {
|
|
||||||
return State::Inactive;
|
|
||||||
}
|
|
||||||
const auto &participants = call->participants();
|
|
||||||
const auto i = ranges::find(
|
|
||||||
participants,
|
|
||||||
user,
|
|
||||||
&Data::GroupCall::Participant::user);
|
|
||||||
if (i == end(participants)) {
|
|
||||||
return State::Inactive;
|
|
||||||
}
|
|
||||||
return !i->muted
|
|
||||||
? State::Active
|
|
||||||
: i->canSelfUnmute
|
|
||||||
? State::Inactive
|
|
||||||
: State::Muted;
|
|
||||||
}
|
|
||||||
|
|
||||||
not_null<const style::IconButton*> Row::ComputeIconStyle(
|
|
||||||
State state) {
|
|
||||||
switch (state) {
|
|
||||||
case State::Inactive: return &st::groupCallInactiveButton;
|
|
||||||
case State::Active: return &st::groupCallActiveButton;
|
|
||||||
case State::Muted: return &st::groupCallMutedButton;
|
|
||||||
}
|
|
||||||
Unexpected("State in Row::ComputeIconStyle.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Row::addActionRipple(QPoint point, Fn<void()> updateCallback) {
|
void Row::addActionRipple(QPoint point, Fn<void()> updateCallback) {
|
||||||
if (!_actionRipple) {
|
if (!_actionRipple) {
|
||||||
auto mask = Ui::RippleAnimation::ellipseMask(
|
auto mask = Ui::RippleAnimation::ellipseMask(QSize(
|
||||||
QSize(_st->rippleAreaSize, _st->rippleAreaSize));
|
st::groupCallActiveButton.rippleAreaSize,
|
||||||
|
st::groupCallActiveButton.rippleAreaSize));
|
||||||
_actionRipple = std::make_unique<Ui::RippleAnimation>(
|
_actionRipple = std::make_unique<Ui::RippleAnimation>(
|
||||||
_st->ripple,
|
st::groupCallActiveButton.ripple,
|
||||||
std::move(mask),
|
std::move(mask),
|
||||||
std::move(updateCallback));
|
std::move(updateCallback));
|
||||||
}
|
}
|
||||||
_actionRipple->add(point - _st->rippleAreaPosition);
|
_actionRipple->add(point - st::groupCallActiveButton.rippleAreaPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Row::stopLastActionRipple() {
|
void Row::stopLastActionRipple() {
|
||||||
|
@ -478,9 +502,17 @@ MembersController::MembersController(
|
||||||
not_null<QWidget*> menuParent)
|
not_null<QWidget*> menuParent)
|
||||||
: _call(call)
|
: _call(call)
|
||||||
, _channel(call->channel())
|
, _channel(call->channel())
|
||||||
, _menuParent(menuParent) {
|
, _menuParent(menuParent)
|
||||||
|
, _inactiveCrossLine(st::groupCallMemberInactiveCrossLine)
|
||||||
|
, _coloredCrossLine(st::groupCallMemberColoredCrossLine) {
|
||||||
setupListChangeViewers(call);
|
setupListChangeViewers(call);
|
||||||
|
|
||||||
|
style::PaletteChanged(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
_inactiveCrossLine.invalidate();
|
||||||
|
_coloredCrossLine.invalidate();
|
||||||
|
}, _lifetime);
|
||||||
|
|
||||||
_speakingAnimation.init([=](crl::time now) {
|
_speakingAnimation.init([=](crl::time now) {
|
||||||
for (const auto [ssrc, row] : _speakingRowBySsrc) {
|
for (const auto [ssrc, row] : _speakingRowBySsrc) {
|
||||||
row->updateBlobAnimation(now);
|
row->updateBlobAnimation(now);
|
||||||
|
@ -746,6 +778,63 @@ auto MembersController::toggleMuteRequests() const
|
||||||
return _toggleMuteRequests.events();
|
return _toggleMuteRequests.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MembersController::rowCanMuteMembers() {
|
||||||
|
return _channel->canManageCall();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MembersController::rowUpdateRow(not_null<Row*> row) {
|
||||||
|
delegate()->peerListUpdateRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MembersController::rowPaintIcon(
|
||||||
|
Painter &p,
|
||||||
|
QRect rect,
|
||||||
|
float64 speaking,
|
||||||
|
float64 active,
|
||||||
|
float64 muted) {
|
||||||
|
const auto &greenIcon = st::groupCallMemberColoredCrossLine.icon;
|
||||||
|
const auto left = rect.x() + (rect.width() - greenIcon.width()) / 2;
|
||||||
|
const auto top = rect.y() + (rect.height() - greenIcon.height()) / 2;
|
||||||
|
if (speaking == 1.) {
|
||||||
|
// Just green icon, no cross, no coloring.
|
||||||
|
greenIcon.paintInCenter(p, rect);
|
||||||
|
return;
|
||||||
|
} else if (speaking == 0.) {
|
||||||
|
if (active == 1.) {
|
||||||
|
// Just gray icon, no cross, no coloring.
|
||||||
|
st::groupCallMemberInactiveCrossLine.icon.paintInCenter(p, rect);
|
||||||
|
return;
|
||||||
|
} else if (active == 0.) {
|
||||||
|
if (muted == 1.) {
|
||||||
|
// Red crossed icon, colorized once, cached as last frame.
|
||||||
|
_coloredCrossLine.paint(
|
||||||
|
p,
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
1.,
|
||||||
|
st::groupCallMemberMutedIcon->c);
|
||||||
|
return;
|
||||||
|
} else if (muted == 0.) {
|
||||||
|
// Gray crossed icon, no coloring, cached as last frame.
|
||||||
|
_inactiveCrossLine.paint(p, left, top, 1.);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto activeInactiveColor = anim::color(
|
||||||
|
st::groupCallMemberInactiveIcon,
|
||||||
|
st::groupCallMemberActiveIcon,
|
||||||
|
speaking);
|
||||||
|
const auto iconColor = anim::color(
|
||||||
|
activeInactiveColor,
|
||||||
|
st::groupCallMemberMutedIcon,
|
||||||
|
muted);
|
||||||
|
|
||||||
|
// Don't use caching of the last frame, because 'muted' may animate color.
|
||||||
|
const auto crossProgress = std::min(1. - active, 0.9999);
|
||||||
|
_inactiveCrossLine.paint(p, left, top, crossProgress, iconColor);
|
||||||
|
}
|
||||||
|
|
||||||
auto MembersController::kickMemberRequests() const
|
auto MembersController::kickMemberRequests() const
|
||||||
-> rpl::producer<not_null<UserData*>>{
|
-> rpl::producer<not_null<UserData*>>{
|
||||||
return _kickMemberRequests.events();
|
return _kickMemberRequests.events();
|
||||||
|
@ -854,14 +943,14 @@ base::unique_qptr<Ui::PopupMenu> MembersController::rowContextMenu(
|
||||||
|
|
||||||
std::unique_ptr<Row> MembersController::createSelfRow() {
|
std::unique_ptr<Row> MembersController::createSelfRow() {
|
||||||
const auto self = _channel->session().user();
|
const auto self = _channel->session().user();
|
||||||
auto result = std::make_unique<Row>(_channel, self);
|
auto result = std::make_unique<Row>(this, self);
|
||||||
updateRow(result.get(), nullptr);
|
updateRow(result.get(), nullptr);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Row> MembersController::createRow(
|
std::unique_ptr<Row> MembersController::createRow(
|
||||||
const Data::GroupCall::Participant &participant) {
|
const Data::GroupCall::Participant &participant) {
|
||||||
auto result = std::make_unique<Row>(_channel, participant.user);
|
auto result = std::make_unique<Row>(this, participant.user);
|
||||||
updateRow(result.get(), &participant);
|
updateRow(result.get(), &participant);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,6 +108,11 @@ public:
|
||||||
, _crossLineMuteAnimation(st::callTopBarMuteCrossLine) {
|
, _crossLineMuteAnimation(st::callTopBarMuteCrossLine) {
|
||||||
resize(_st.width, _st.height);
|
resize(_st.width, _st.height);
|
||||||
installEventFilter(this);
|
installEventFilter(this);
|
||||||
|
|
||||||
|
style::PaletteChanged(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
_crossLineMuteAnimation.invalidate();
|
||||||
|
}, lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void setProgress(float64 progress) {
|
void setProgress(float64 progress) {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 87ee83bc7336b4c814e2f0dc45261ff8e280cca0
|
Subproject commit ab4ad89c4c709b2ec0f8296451d49c99d2ae4372
|
Loading…
Add table
Reference in a new issue