Extract MembersRow from calls_group_members module.

This commit is contained in:
John Preston 2021-05-07 17:01:54 +04:00
parent d38780c94d
commit 80e3e8a01e
8 changed files with 1160 additions and 1086 deletions

View File

@ -279,6 +279,8 @@ PRIVATE
calls/group/calls_group_common.h
calls/group/calls_group_members.cpp
calls/group/calls_group_members.h
calls/group/calls_group_members_row.cpp
calls/group/calls_group_members_row.h
calls/group/calls_group_menu.cpp
calls/group/calls_group_menu.h
calls/group/calls_group_panel.cpp

View File

@ -56,7 +56,7 @@ constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000);
settings.callVideoInputDeviceId());
}
[[nodiscard]] const Data::GroupCall::Participant *LookupParticipant(
[[nodiscard]] const Data::GroupCallParticipant *LookupParticipant(
not_null<PeerData*> peer,
uint64 id,
not_null<PeerData*> participantPeer) {
@ -68,7 +68,7 @@ constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000);
const auto i = ranges::find(
participants,
participantPeer,
&Data::GroupCall::Participant::peer);
&Data::GroupCallParticipant::peer);
return (i != end(participants)) ? &*i : nullptr;
}
@ -1149,7 +1149,7 @@ void GroupCall::applyMeInCallLocally() {
const auto i = ranges::find(
participants,
_joinAs,
&Data::GroupCall::Participant::peer);
&Data::GroupCallParticipant::peer);
const auto date = (i != end(participants))
? i->date
: base::unixtime::now();
@ -2586,7 +2586,7 @@ std::variant<int, not_null<UserData*>> GroupCall::inviteUsers(
return !invited.contains(user) && !ranges::contains(
participants,
user,
&Data::GroupCall::Participant::peer);
&Data::GroupCallParticipant::peer);
});
auto count = 0;

View File

@ -13,6 +13,7 @@ namespace Calls::Group {
constexpr auto kDefaultVolume = 10000;
constexpr auto kMaxVolume = 20000;
constexpr auto kBlobsEnterDuration = crl::time(250);
struct MuteRequest {
not_null<PeerData*> peer;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,890 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/group/calls_group_members_row.h"
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_common.h"
#include "data/data_peer.h"
#include "data/data_group_call.h"
#include "ui/paint/arcs.h"
#include "ui/paint/blobs.h"
#include "ui/text/text_options.h"
#include "ui/effects/ripple_animation.h"
#include "lang/lang_keys.h"
#include "webrtc/webrtc_video_track.h"
#include "styles/style_calls.h"
namespace Calls::Group {
namespace {
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 kWideScale = 5;
constexpr auto kArcsStrokeRatio = 0.8;
const auto kSpeakerThreshold = std::vector<float>{
Group::kDefaultVolume * 0.1f / Group::kMaxVolume,
Group::kDefaultVolume * 0.9f / Group::kMaxVolume };
auto RowBlobs() -> std::array<Ui::Paint::Blobs::BlobData, 2> {
return { {
{
.segmentsCount = 6,
.minScale = kBlobScale * kMinorBlobFactor,
.minRadius = st::groupCallRowBlobMinRadius * kMinorBlobFactor,
.maxRadius = st::groupCallRowBlobMaxRadius * kMinorBlobFactor,
.speedScale = 1.,
.alpha = .5,
},
{
.segmentsCount = 8,
.minScale = kBlobScale,
.minRadius = (float)st::groupCallRowBlobMinRadius,
.maxRadius = (float)st::groupCallRowBlobMaxRadius,
.speedScale = 1.,
.alpha = .2,
},
} };
}
[[nodiscard]] QString StatusPercentString(float volume) {
return QString::number(int(std::round(volume * 200))) + '%';
}
[[nodiscard]] int StatusPercentWidth(const QString &percent) {
return st::normalFont->width(percent);
}
} // namespace
struct MembersRow::BlobsAnimation {
BlobsAnimation(
std::vector<Ui::Paint::Blobs::BlobData> blobDatas,
float levelDuration,
float maxLevel);
Ui::Paint::Blobs blobs;
crl::time lastTime = 0;
crl::time lastSoundingUpdateTime = 0;
float64 enter = 0.;
QImage userpicCache;
InMemoryKey userpicKey;
rpl::lifetime lifetime;
};
struct MembersRow::StatusIcon {
StatusIcon(bool shown, float volume);
const style::icon &speaker;
Ui::Paint::ArcsAnimation arcs;
Ui::Animations::Simple arcsAnimation;
Ui::Animations::Simple shownAnimation;
QString percent;
int percentWidth = 0;
int arcsWidth = 0;
int wasArcsWidth = 0;
bool shown = true;
rpl::lifetime lifetime;
};
MembersRow::BlobsAnimation::BlobsAnimation(
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);
}
MembersRow::StatusIcon::StatusIcon(bool shown, float volume)
: speaker(st::groupCallStatusSpeakerIcon)
, arcs(
st::groupCallStatusSpeakerArcsAnimation,
kSpeakerThreshold,
volume,
Ui::Paint::ArcsAnimation::Direction::Right)
, percent(StatusPercentString(volume))
, percentWidth(StatusPercentWidth(percent))
, shown(shown) {
}
MembersRow::MembersRow(
not_null<MembersRowDelegate*> delegate,
not_null<PeerData*> participantPeer)
: PeerListRow(participantPeer)
, _delegate(delegate) {
refreshStatus();
_aboutText = participantPeer->about();
}
MembersRow::~MembersRow() = default;
void MembersRow::setSkipLevelUpdate(bool value) {
_skipLevelUpdate = value;
}
void MembersRow::updateState(
const Data::GroupCallParticipant *participant) {
setSsrc(participant ? participant->ssrc : 0);
setVolume(participant
? participant->volume
: Group::kDefaultVolume);
if (!participant) {
setState(State::Invited);
setSounding(false);
setSpeaking(false);
_raisedHandRating = 0;
} else if (!participant->muted
|| (participant->sounding && participant->ssrc != 0)) {
setState(participant->mutedByMe ? State::MutedByMe : State::Active);
setSounding(participant->sounding && participant->ssrc != 0);
setSpeaking(participant->speaking && participant->ssrc != 0);
_raisedHandRating = 0;
} else if (participant->canSelfUnmute) {
setState(participant->mutedByMe
? State::MutedByMe
: State::Inactive);
setSounding(false);
setSpeaking(false);
_raisedHandRating = 0;
} else {
_raisedHandRating = participant->raisedHandRating;
setState(_raisedHandRating ? State::RaisedHand : State::Muted);
setSounding(false);
setSpeaking(false);
}
refreshStatus();
}
void MembersRow::setSpeaking(bool speaking) {
if (_speaking == speaking) {
return;
}
_speaking = speaking;
_speakingAnimation.start(
[=] { _delegate->rowUpdateRow(this); },
_speaking ? 0. : 1.,
_speaking ? 1. : 0.,
st::widgetFadeDuration);
if (!_speaking
|| (_state == State::MutedByMe)
|| (_state == State::Muted)
|| (_state == State::RaisedHand)) {
if (_statusIcon) {
_statusIcon = nullptr;
_delegate->rowUpdateRow(this);
}
} else if (!_statusIcon) {
_statusIcon = std::make_unique<StatusIcon>(
(_volume != Group::kDefaultVolume),
(float)_volume / Group::kMaxVolume);
_statusIcon->arcs.setStrokeRatio(kArcsStrokeRatio);
_statusIcon->arcsWidth = _statusIcon->arcs.finishedWidth();
_statusIcon->arcs.startUpdateRequests(
) | rpl::start_with_next([=] {
if (!_statusIcon->arcsAnimation.animating()) {
_statusIcon->wasArcsWidth = _statusIcon->arcsWidth;
}
auto callback = [=](float64 value) {
_statusIcon->arcs.update(crl::now());
_statusIcon->arcsWidth = anim::interpolate(
_statusIcon->wasArcsWidth,
_statusIcon->arcs.finishedWidth(),
value);
_delegate->rowUpdateRow(this);
};
_statusIcon->arcsAnimation.start(
std::move(callback),
0.,
1.,
st::groupCallSpeakerArcsAnimation.duration);
}, _statusIcon->lifetime);
}
}
void MembersRow::setSounding(bool sounding) {
if (_sounding == sounding) {
return;
}
_sounding = sounding;
if (!_sounding) {
_blobsAnimation = nullptr;
} else if (!_blobsAnimation) {
_blobsAnimation = std::make_unique<BlobsAnimation>(
RowBlobs() | ranges::to_vector,
kLevelDuration,
kMaxLevel);
_blobsAnimation->lastTime = crl::now();
updateLevel(GroupCall::kSpeakLevelThreshold);
}
}
void MembersRow::clearRaisedHandStatus() {
if (!_raisedHandStatus) {
return;
}
_raisedHandStatus = false;
refreshStatus();
_delegate->rowUpdateRow(this);
}
void MembersRow::setState(State state) {
if (_state == state) {
return;
}
const auto wasActive = (_state == State::Active);
const auto wasMuted = (_state == State::Muted)
|| (_state == State::RaisedHand);
const auto wasRaisedHand = (_state == State::RaisedHand);
_state = state;
const auto nowActive = (_state == State::Active);
const auto nowMuted = (_state == State::Muted)
|| (_state == State::RaisedHand);
const auto nowRaisedHand = (_state == State::RaisedHand);
if (!wasRaisedHand && nowRaisedHand) {
_raisedHandStatus = true;
_delegate->rowScheduleRaisedHandStatusRemove(this);
}
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 MembersRow::setSsrc(uint32 ssrc) {
_ssrc = ssrc;
}
void MembersRow::setVolume(int volume) {
_volume = volume;
if (_statusIcon) {
const auto floatVolume = (float)volume / Group::kMaxVolume;
_statusIcon->arcs.setValue(floatVolume);
_statusIcon->percent = StatusPercentString(floatVolume);
_statusIcon->percentWidth = StatusPercentWidth(_statusIcon->percent);
const auto shown = (volume != Group::kDefaultVolume);
if (_statusIcon->shown != shown) {
_statusIcon->shown = shown;
_statusIcon->shownAnimation.start(
[=] { _delegate->rowUpdateRow(this); },
shown ? 0. : 1.,
shown ? 1. : 0.,
st::groupCallSpeakerArcsAnimation.duration);
}
}
}
void MembersRow::updateLevel(float level) {
Expects(_blobsAnimation != nullptr);
const auto spoke = (level >= GroupCall::kSpeakLevelThreshold)
? crl::now()
: crl::time();
if (spoke && _speaking) {
_speakingLastTime = spoke;
}
if (_skipLevelUpdate) {
return;
}
if (spoke) {
_blobsAnimation->lastSoundingUpdateTime = spoke;
}
_blobsAnimation->blobs.setLevel(level);
}
void MembersRow::updateBlobAnimation(crl::time now) {
Expects(_blobsAnimation != nullptr);
const auto soundingFinishesAt = _blobsAnimation->lastSoundingUpdateTime
+ Data::GroupCall::kSoundStatusKeptFor;
const auto soundingStartsFinishing = soundingFinishesAt
- kBlobsEnterDuration;
const auto soundingFinishes = (soundingStartsFinishing < now);
if (soundingFinishes) {
_blobsAnimation->enter = std::clamp(
(soundingFinishesAt - now) / float64(kBlobsEnterDuration),
0.,
1.);
} else if (_blobsAnimation->enter < 1.) {
_blobsAnimation->enter = std::clamp(
(_blobsAnimation->enter
+ ((now - _blobsAnimation->lastTime)
/ float64(kBlobsEnterDuration))),
0.,
1.);
}
_blobsAnimation->blobs.updateLevel(now - _blobsAnimation->lastTime);
_blobsAnimation->lastTime = now;
}
void MembersRow::ensureUserpicCache(
std::shared_ptr<Data::CloudImageView> &view,
int size) {
Expects(_blobsAnimation != nullptr);
const auto user = peer();
const auto key = user->userpicUniqueKey(view);
const auto full = QSize(size, size) * kWideScale * cIntRetinaFactor();
auto &cache = _blobsAnimation->userpicCache;
if (cache.isNull()) {
cache = QImage(full, QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(cRetinaFactor());
} else if (_blobsAnimation->userpicKey == key
&& cache.size() == full) {
return;
}
_blobsAnimation->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);
}
}
bool MembersRow::paintVideo(
Painter &p,
int x,
int y,
int sizew,
int sizeh,
PanelMode mode) {
if (!_videoTrackShown) {
return false;
}
const auto guard = gsl::finally([&] {
_videoTrackShown->markFrameShown();
});
const auto videoSize = _videoTrackShown->frameSize();
if (videoSize.isEmpty()
|| _videoTrackShown->state() != Webrtc::VideoState::Active) {
return false;
}
const auto videow = videoSize.width();
const auto videoh = videoSize.height();
const auto resize = (videow * sizeh > videoh * sizew)
? QSize(videow * sizeh / videoh, sizeh)
: QSize(sizew, videoh * sizew / videow);
const auto request = Webrtc::FrameRequest{
.resize = resize * cIntRetinaFactor(),
.outer = QSize(sizew, sizeh) * cIntRetinaFactor(),
};
const auto frame = _videoTrackShown->frame(request);
auto copy = frame; // #TODO calls optimize.
copy.detach();
if (mode == PanelMode::Default) {
Images::prepareCircle(copy);
} else {
Images::prepareRound(copy, ImageRoundRadius::Large);
}
p.drawImage(
QRect(QPoint(x, y), copy.size() / cIntRetinaFactor()),
copy);
return true;
}
std::tuple<int, int, int> MembersRow::UserpicInNarrowMode(
int x,
int y,
int sizew,
int sizeh) {
const auto useSize = st::groupCallMembersList.item.photoSize;
const auto skipx = (sizew - useSize) / 2;
return { x + skipx, y + st::groupCallNarrowUserpicTop, useSize };
}
void MembersRow::paintBlobs(
Painter &p,
int x,
int y,
int sizew,
int sizeh,
PanelMode mode) {
if (!_blobsAnimation) {
return;
}
auto size = sizew;
if (mode == PanelMode::Wide) {
std::tie(x, y, size) = UserpicInNarrowMode(x, y, sizew, sizeh);
}
const auto mutedByMe = (_state == State::MutedByMe);
const auto shift = QPointF(x + size / 2., y + size / 2.);
auto hq = PainterHighQualityEnabler(p);
p.translate(shift);
const auto brush = mutedByMe
? st::groupCallMemberMutedIcon->b
: anim::brush(
st::groupCallMemberInactiveStatus,
st::groupCallMemberActiveStatus,
_speakingAnimation.value(_speaking ? 1. : 0.));
_blobsAnimation->blobs.paint(p, brush);
p.translate(-shift);
p.setOpacity(1.);
}
void MembersRow::paintScaledUserpic(
Painter &p,
std::shared_ptr<Data::CloudImageView> &userpic,
int x,
int y,
int outerWidth,
int sizew,
int sizeh,
PanelMode mode) {
auto size = sizew;
if (mode == PanelMode::Wide) {
std::tie(x, y, size) = UserpicInNarrowMode(x, y, sizew, sizeh);
}
if (!_blobsAnimation) {
peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
return;
}
const auto enter = _blobsAnimation->enter;
const auto &minScale = kUserpicMinScale;
const auto scaleUserpic = minScale
+ (1. - minScale) * _blobsAnimation->blobs.currentLevel();
const auto scale = scaleUserpic * enter + 1. * (1. - enter);
if (scale == 1.) {
peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
return;
}
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),
_blobsAnimation->userpicCache);
}
void MembersRow::paintNarrowName(
Painter &p,
int x,
int y,
int sizew,
int sizeh,
NarrowStyle style) {
if (_narrowName.isEmpty()) {
_narrowName.setText(
st::semiboldTextStyle,
generateShortName(),
Ui::NameTextOptions());
}
if (style == NarrowStyle::Video) {
_delegate->rowPaintNarrowShadow(p, x, y, sizew, sizeh);
}
const auto &icon = st::groupCallVideoCrossLine.icon;
const auto added = icon.width() - st::groupCallNarrowIconLess;
const auto available = sizew - 2 * st::normalFont->spacew - added;
const auto use = std::min(available, _narrowName.maxWidth());
const auto left = x + (sizew - use - added) / 2;
const auto iconRect = QRect(
left - st::groupCallNarrowIconLess,
y + st::groupCallNarrowIconTop,
icon.width(),
icon.height());
const auto &state = computeIconState(style);
_delegate->rowPaintIcon(p, iconRect, state);
p.setPen([&] {
if (style == NarrowStyle::Video) {
return st::groupCallVideoTextFg->p;
} else if (state.speaking == 1. && !state.mutedByMe) {
return st::groupCallMemberActiveIcon->p;
} else if (state.speaking == 0.) {
if (state.active == 1.) {
return st::groupCallMemberInactiveIcon->p;
} else if (state.active == 0.) {
if (state.muted == 1.) {
return state.raisedHand
? st::groupCallMemberInactiveStatus->p
: st::groupCallMemberMutedIcon->p;
} else if (state.muted == 0.) {
return st::groupCallMemberInactiveIcon->p;
}
}
}
const auto activeInactiveColor = anim::color(
st::groupCallMemberInactiveIcon,
(state.mutedByMe
? st::groupCallMemberMutedIcon
: st::groupCallMemberActiveIcon),
state.speaking);
return anim::pen(
activeInactiveColor,
st::groupCallMemberMutedIcon,
state.muted);
}());
const auto nameLeft = iconRect.x() + icon.width();
const auto nameTop = y + st::groupCallNarrowNameTop;
if (use == available) {
_narrowName.drawLeftElided(p, nameLeft, nameTop, available, sizew);
} else {
_narrowName.drawLeft(p, nameLeft, nameTop, available, sizew);
}
}
auto MembersRow::generatePaintUserpicCallback() -> PaintRoundImageCallback {
return [=](Painter &p, int x, int y, int outerWidth, int size) {
const auto outer = outerWidth;
paintComplexUserpic(p, x, y, outer, size, size, PanelMode::Default);
};
}
void MembersRow::paintComplexUserpic(
Painter &p,
int x,
int y,
int outerWidth,
int sizew,
int sizeh,
PanelMode mode,
bool selected) {
if (mode == PanelMode::Wide) {
if (paintVideo(p, x, y, sizew, sizeh, mode)) {
paintNarrowName(p, x, y, sizew, sizeh, NarrowStyle::Video);
_delegate->rowPaintNarrowBorder(p, x, y, this);
return;
}
_delegate->rowPaintNarrowBackground(p, x, y, selected);
paintRipple(p, x, y, outerWidth);
}
paintBlobs(p, x, y, sizew, sizeh, mode);
if (mode == PanelMode::Default
&& paintVideo(p, x, y, sizew, sizeh, mode)) {
return;
}
paintScaledUserpic(
p,
ensureUserpicView(),
x,
y,
outerWidth,
sizew,
sizeh,
mode);
if (mode == PanelMode::Wide) {
paintNarrowName(p, x, y, sizew, sizeh, NarrowStyle::Userpic);
_delegate->rowPaintNarrowBorder(p, x, y, this);
}
}
int MembersRow::statusIconWidth() const {
if (!_statusIcon || !_speaking) {
return 0;
}
const auto shown = _statusIcon->shownAnimation.value(
_statusIcon->shown ? 1. : 0.);
const auto full = _statusIcon->speaker.width()
+ _statusIcon->arcsWidth
+ _statusIcon->percentWidth
+ st::normalFont->spacew;
return int(std::round(shown * full));
}
int MembersRow::statusIconHeight() const {
return (_statusIcon && _speaking) ? _statusIcon->speaker.height() : 0;
}
void MembersRow::paintStatusIcon(
Painter &p,
const style::PeerListItem &st,
const style::font &font,
bool selected) {
if (!_statusIcon) {
return;
}
const auto shown = _statusIcon->shownAnimation.value(
_statusIcon->shown ? 1. : 0.);
if (shown == 0.) {
return;
}
p.setFont(font);
const auto color = (_speaking
? st.statusFgActive
: (selected ? st.statusFgOver : st.statusFg))->c;
p.setPen(color);
const auto speakerRect = QRect(
st.statusPosition
+ QPoint(0, (font->height - statusIconHeight()) / 2),
_statusIcon->speaker.size());
const auto arcPosition = speakerRect.topLeft()
+ QPoint(
speakerRect.width() - st::groupCallStatusSpeakerArcsSkip,
speakerRect.height() / 2);
const auto fullWidth = speakerRect.width()
+ _statusIcon->arcsWidth
+ _statusIcon->percentWidth
+ st::normalFont->spacew;
p.save();
if (shown < 1.) {
const auto centerx = speakerRect.x() + fullWidth / 2;
const auto centery = speakerRect.y() + speakerRect.height() / 2;
p.translate(centerx, centery);
p.scale(shown, shown);
p.translate(-centerx, -centery);
}
_statusIcon->speaker.paint(
p,
speakerRect.topLeft(),
speakerRect.width(),
color);
p.translate(arcPosition);
_statusIcon->arcs.paint(p, color);
p.translate(-arcPosition);
p.setFont(st::normalFont);
p.setPen(st.statusFgActive);
p.drawTextLeft(
st.statusPosition.x() + speakerRect.width() + _statusIcon->arcsWidth,
st.statusPosition.y(),
fullWidth,
_statusIcon->percent);
p.restore();
}
void MembersRow::setAbout(const QString &about) {
if (_aboutText == about) {
return;
}
_aboutText = about;
_delegate->rowUpdateRow(this);
}
void MembersRow::paintStatusText(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int availableWidth,
int outerWidth,
bool selected) {
const auto &font = st::normalFont;
const auto about = (_state == State::Inactive
|| _state == State::Muted
|| (_state == State::RaisedHand && !_raisedHandStatus))
? _aboutText
: QString();
if (about.isEmpty()
&& _state != State::Invited
&& _state != State::MutedByMe) {
paintStatusIcon(p, st, font, selected);
const auto translatedWidth = statusIconWidth();
p.translate(translatedWidth, 0);
const auto guard = gsl::finally([&] {
p.translate(-translatedWidth, 0);
});
PeerListRow::paintStatusText(
p,
st,
x,
y,
availableWidth - translatedWidth,
outerWidth,
selected);
return;
}
p.setFont(font);
if (_state == State::MutedByMe) {
p.setPen(st::groupCallMemberMutedIcon);
} else {
p.setPen(st::groupCallMemberNotJoinedStatus);
}
p.drawTextLeft(
x,
y,
outerWidth,
(_state == State::MutedByMe
? tr::lng_group_call_muted_by_me_status(tr::now)
: !about.isEmpty()
? font->m.elidedText(about, Qt::ElideRight, availableWidth)
: _delegate->rowIsMe(peer())
? tr::lng_status_connecting(tr::now)
: tr::lng_group_call_invited_status(tr::now)));
}
QSize MembersRow::actionSize() const {
return QSize(
st::groupCallActiveButton.width,
st::groupCallActiveButton.height);
}
bool MembersRow::actionDisabled() const {
return _delegate->rowIsMe(peer())
|| (_state == State::Invited)
|| !_delegate->rowCanMuteMembers();
}
QMargins MembersRow::actionMargins() const {
return QMargins(
0,
0,
st::groupCallMemberButtonSkip,
0);
}
void MembersRow::paintAction(
Painter &p,
int x,
int y,
int outerWidth,
bool selected,
bool actionSelected) {
auto size = actionSize();
const auto iconRect = style::rtlrect(
x,
y,
size.width(),
size.height(),
outerWidth);
if (_state == State::Invited) {
_actionRipple = nullptr;
st::groupCallMemberInvited.paint(
p,
QPoint(x, y) + st::groupCallMemberInvitedPosition,
outerWidth);
return;
}
if (_actionRipple) {
_actionRipple->paint(
p,
x + st::groupCallActiveButton.rippleAreaPosition.x(),
y + st::groupCallActiveButton.rippleAreaPosition.y(),
outerWidth);
if (_actionRipple->empty()) {
_actionRipple.reset();
}
}
_delegate->rowPaintIcon(p, iconRect, computeIconState());
}
MembersRowDelegate::IconState MembersRow::computeIconState(
NarrowStyle style) const {
const auto speaking = _speakingAnimation.value(_speaking ? 1. : 0.);
const auto active = _activeAnimation.value(
(_state == State::Active) ? 1. : 0.);
const auto muted = _mutedAnimation.value(
(_state == State::Muted || _state == State::RaisedHand) ? 1. : 0.);
const auto mutedByMe = (_state == State::MutedByMe);
return {
.speaking = speaking,
.active = active,
.muted = muted,
.mutedByMe = (_state == State::MutedByMe),
.raisedHand = (_state == State::RaisedHand),
.narrowStyle = style,
};
}
void MembersRow::refreshStatus() {
setCustomStatus(
(_speaking
? tr::lng_group_call_active(tr::now)
: _raisedHandStatus
? tr::lng_group_call_raised_hand_status(tr::now)
: tr::lng_group_call_inactive(tr::now)),
_speaking);
}
not_null<Webrtc::VideoTrack*> MembersRow::createVideoTrack(
const std::string &endpoint) {
_videoTrackShown = nullptr;
_videoTrackEndpoint = endpoint;
_videoTrack = std::make_unique<Webrtc::VideoTrack>(
Webrtc::VideoState::Active);
setVideoTrack(_videoTrack.get());
return _videoTrack.get();
}
const std::string &MembersRow::videoTrackEndpoint() const {
return _videoTrackEndpoint;
}
void MembersRow::clearVideoTrack() {
_videoTrackLifetime.destroy();
_videoTrackEndpoint = std::string();
_videoTrackShown = nullptr;
_videoTrack = nullptr;
_delegate->rowUpdateRow(this);
}
void MembersRow::setVideoTrack(not_null<Webrtc::VideoTrack*> track) {
_videoTrackLifetime.destroy();
_videoTrackShown = track;
_videoTrackShown->renderNextFrame(
) | rpl::start_with_next([=] {
_delegate->rowUpdateRow(this);
if (_videoTrackShown->frameSize().isEmpty()) {
_videoTrackShown->markFrameShown();
}
}, _videoTrackLifetime);
_delegate->rowUpdateRow(this);
}
void MembersRow::addActionRipple(QPoint point, Fn<void()> updateCallback) {
if (!_actionRipple) {
auto mask = Ui::RippleAnimation::ellipseMask(QSize(
st::groupCallActiveButton.rippleAreaSize,
st::groupCallActiveButton.rippleAreaSize));
_actionRipple = std::make_unique<Ui::RippleAnimation>(
st::groupCallActiveButton.ripple,
std::move(mask),
std::move(updateCallback));
}
_actionRipple->add(point - st::groupCallActiveButton.rippleAreaPosition);
}
void MembersRow::refreshName(const style::PeerListItem &st) {
PeerListRow::refreshName(st);
_narrowName = Ui::Text::String();
}
void MembersRow::stopLastActionRipple() {
if (_actionRipple) {
_actionRipple->lastStop();
}
}
} // namespace Calls::Group

View File

@ -0,0 +1,243 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "boxes/peer_list_box.h"
#include "calls/group/calls_group_common.h"
class PeerData;
class Painter;
namespace Webrtc {
class VideoTrack;
} // namespace Webrtc
namespace Data {
struct GroupCallParticipant;
} // namespace Data
namespace Ui {
class RippleAnimation;
} // namespace Ui
namespace Calls::Group {
enum class NarrowStyle {
None,
Userpic,
Video,
};
class MembersRow;
class MembersRowDelegate {
public:
struct IconState {
float64 speaking = 0.;
float64 active = 0.;
float64 muted = 0.;
bool mutedByMe = false;
bool raisedHand = false;
NarrowStyle narrowStyle = NarrowStyle::None;
};
virtual bool rowIsMe(not_null<PeerData*> participantPeer) = 0;
virtual bool rowCanMuteMembers() = 0;
virtual void rowUpdateRow(not_null<MembersRow*> row) = 0;
virtual void rowScheduleRaisedHandStatusRemove(not_null<MembersRow*> row) = 0;
virtual void rowPaintIcon(
Painter &p,
QRect rect,
const IconState &state) = 0;
virtual void rowPaintNarrowBackground(
Painter &p,
int x,
int y,
bool selected) = 0;
virtual void rowPaintNarrowBorder(
Painter &p,
int x,
int y,
not_null<MembersRow*> row) = 0;
virtual void rowPaintNarrowShadow(
Painter &p,
int x,
int y,
int sizew,
int sizeh) = 0;
};
class MembersRow final : public PeerListRow {
public:
MembersRow(
not_null<MembersRowDelegate*> delegate,
not_null<PeerData*> participantPeer);
~MembersRow();
enum class State {
Active,
Inactive,
Muted,
RaisedHand,
MutedByMe,
Invited,
};
void setAbout(const QString &about);
void setSkipLevelUpdate(bool value);
void updateState(const Data::GroupCallParticipant *participant);
void updateLevel(float level);
void updateBlobAnimation(crl::time now);
void clearRaisedHandStatus();
[[nodiscard]] State state() const {
return _state;
}
[[nodiscard]] uint32 ssrc() const {
return _ssrc;
}
[[nodiscard]] bool sounding() const {
return _sounding;
}
[[nodiscard]] bool speaking() const {
return _speaking;
}
[[nodiscard]] crl::time speakingLastTime() const {
return _speakingLastTime;
}
[[nodiscard]] int volume() const {
return _volume;
}
[[nodiscard]] uint64 raisedHandRating() const {
return _raisedHandRating;
}
[[nodiscard]] not_null<Webrtc::VideoTrack*> createVideoTrack(
const std::string &endpoint);
void clearVideoTrack();
[[nodiscard]] const std::string &videoTrackEndpoint() const;
void setVideoTrack(not_null<Webrtc::VideoTrack*> track);
void addActionRipple(QPoint point, Fn<void()> updateCallback) override;
void stopLastActionRipple() override;
void refreshName(const style::PeerListItem &st) override;
QSize actionSize() const override;
bool actionDisabled() const override;
QMargins actionMargins() const override;
void paintAction(
Painter &p,
int x,
int y,
int outerWidth,
bool selected,
bool actionSelected) override;
PaintRoundImageCallback generatePaintUserpicCallback() override;
void paintComplexUserpic(
Painter &p,
int x,
int y,
int outerWidth,
int sizew,
int sizeh,
PanelMode mode,
bool selected = false);
void paintStatusText(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int availableWidth,
int outerWidth,
bool selected) override;
private:
struct BlobsAnimation;
struct StatusIcon;
int statusIconWidth() const;
int statusIconHeight() const;
void paintStatusIcon(
Painter &p,
const style::PeerListItem &st,
const style::font &font,
bool selected);
void refreshStatus() override;
void setSounding(bool sounding);
void setSpeaking(bool speaking);
void setState(State state);
void setSsrc(uint32 ssrc);
void setVolume(int volume);
void ensureUserpicCache(
std::shared_ptr<Data::CloudImageView> &view,
int size);
bool paintVideo(
Painter &p,
int x,
int y,
int sizew,
int sizeh,
PanelMode mode);
[[nodiscard]] static std::tuple<int, int, int> UserpicInNarrowMode(
int x,
int y,
int sizew,
int sizeh);
void paintBlobs(
Painter &p,
int x,
int y,
int sizew,
int sizeh, PanelMode mode);
void paintScaledUserpic(
Painter &p,
std::shared_ptr<Data::CloudImageView> &userpic,
int x,
int y,
int outerWidth,
int sizew,
int sizeh,
PanelMode mode);
void paintNarrowName(
Painter &p,
int x,
int y,
int sizew,
int sizeh,
NarrowStyle style);
[[nodiscard]] MembersRowDelegate::IconState computeIconState(
NarrowStyle style = NarrowStyle::None) const;
const not_null<MembersRowDelegate*> _delegate;
State _state = State::Inactive;
std::unique_ptr<Ui::RippleAnimation> _actionRipple;
std::unique_ptr<BlobsAnimation> _blobsAnimation;
std::unique_ptr<StatusIcon> _statusIcon;
std::unique_ptr<Webrtc::VideoTrack> _videoTrack;
Webrtc::VideoTrack *_videoTrackShown = nullptr;
std::string _videoTrackEndpoint;
rpl::lifetime _videoTrackLifetime; // #TODO calls move to unique_ptr.
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.
Ui::Text::String _narrowName;
QString _aboutText;
crl::time _speakingLastTime = 0;
uint64 _raisedHandRating = 0;
uint32 _ssrc = 0;
int _volume = Group::kDefaultVolume;
bool _sounding = false;
bool _speaking = false;
bool _raisedHandStatus = false;
bool _skipLevelUpdate = false;
};
} // namespace Calls::Group

View File

@ -834,7 +834,7 @@ void Session::registerInvitedToCallUser(
const auto inCall = ranges::contains(
call->participants(),
user,
&Data::GroupCall::Participant::peer);
&Data::GroupCallParticipant::peer);
if (inCall) {
return;
}

View File

@ -78,7 +78,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
};
// speaking DESC, std::max(date, lastActive) DESC
static const auto SortKey = [](const Data::GroupCall::Participant &p) {
static const auto SortKey = [](const Data::GroupCallParticipant &p) {
const auto result = (p.speaking ? uint64(0x100000000ULL) : uint64(0))
| uint64(std::max(p.lastActive, p.date));
return (~uint64(0)) - result; // sorting with less(), so invert.
@ -93,7 +93,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
if (already >= kLimit || participants.size() <= already) {
return false;
}
std::array<const Data::GroupCall::Participant*, kLimit> adding{
std::array<const Data::GroupCallParticipant*, kLimit> adding{
{ nullptr }
};
for (const auto &participant : call->participants()) {
@ -200,7 +200,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
const auto j = ranges::find(
participants,
i->peer,
&Data::GroupCall::Participant::peer);
&Data::GroupCallParticipant::peer);
if (j == end(participants) || !j->speaking) {
// Found a non-speaking one, put the new speaking one here.
break;
@ -231,7 +231,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
const auto j = ranges::find(
participants,
i->peer,
&Data::GroupCall::Participant::peer);
&Data::GroupCallParticipant::peer);
if (j == end(participants) || !j->speaking) {
// Found a non-speaking one, remove.
state->userpics.erase(i);