Show name and information on wide large video.

This commit is contained in:
John Preston 2021-05-07 19:37:28 +04:00
parent 80e3e8a01e
commit 50558de591
23 changed files with 613 additions and 222 deletions

View File

@ -277,6 +277,8 @@ PRIVATE
calls/group/calls_group_call.cpp
calls/group/calls_group_call.h
calls/group/calls_group_common.h
calls/group/calls_group_large_video.cpp
calls/group/calls_group_large_video.h
calls/group/calls_group_members.cpp
calls/group/calls_group_members.h
calls/group/calls_group_members_row.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1001 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1171,3 +1171,48 @@ groupCallVideoCrossLine: CrossLineAnimation(groupCallNarrowInactiveCrossLine) {
fg: groupCallVideoTextFg;
icon: icon {{ "calls/voice_mute_mini", groupCallVideoTextFg }};
}
groupCallLargeVideoCrossLine: CrossLineAnimation(groupCallMemberColoredCrossLine) {
fg: groupCallVideoTextFg;
icon: icon {{ "calls/group_calls_unmuted", groupCallVideoSubTextFg }};
}
GroupCallLargeVideo {
shadowHeight: pixels;
controlsAlign: align;
namePosition: point;
statusPosition: point;
pinPosition: point;
iconPosition: point;
}
groupCallLargeVideoWide: GroupCallLargeVideo {
shadowHeight: 60px;
controlsAlign: align(top);
namePosition: point(15px, 8px);
statusPosition: point(15px, 28px);
pinPosition: point(52px, 15px);
iconPosition: point(14px, 15px);
}
groupCallLargeVideoNarrow: GroupCallLargeVideo {
shadowHeight: 50px;
controlsAlign: align(top);
namePosition: point(64px, 44px);
statusPosition: point(64px, 20px);
pinPosition: point(20px, 12px);
iconPosition: point(18px, 12px);
}
groupCallLargeVideoListItem: PeerListItem(groupCallMembersListItem) {
nameFg: groupCallVideoTextFg;
nameFgChecked: groupCallVideoTextFg;
statusFg: groupCallVideoSubTextFg;
statusFgOver: groupCallVideoSubTextFg;
statusFgActive: groupCallVideoSubTextFg;
}
groupCallLargeVideoPin: CrossLineAnimation {
fg: groupCallVideoSubTextFg;
icon: icon {{ "calls/voice_pin", groupCallVideoSubTextFg }};
startPosition: point(5px, 2px);
endPosition: point(20px, 17px);
stroke: 2px;
}

View File

@ -170,14 +170,6 @@ private:
};
struct GroupCall::LargeTrack {
LargeTrack() : track(Webrtc::VideoState::Active) {
}
Webrtc::VideoTrack track;
std::shared_ptr<Webrtc::SinkInterface> sink;
};
struct GroupCall::SinkPointer {
std::shared_ptr<Webrtc::SinkInterface> data;
};
@ -1847,18 +1839,18 @@ void GroupCall::ensureControllerCreated() {
_videoEndpointLarge.changes(
) | rpl::start_with_next([=](const VideoEndpoint &endpoint) {
_instance->setFullSizeVideoEndpointId(endpoint.endpoint);
_videoLargeTrack = nullptr;
_videoLargeTrack = LargeTrack();
_videoLargeTrackWrap = nullptr;
if (endpoint.empty()) {
if (!endpoint) {
return;
}
if (!_videoLargeTrackWrap) {
_videoLargeTrackWrap = std::make_unique<LargeTrack>();
_videoLargeTrack = &_videoLargeTrackWrap->track;
}
_videoLargeTrackWrap->sink = Webrtc::CreateProxySink(
_videoLargeTrackWrap->track.sink());
addVideoOutput(endpoint.endpoint, { _videoLargeTrackWrap->sink });
_videoLargeTrackWrap = std::make_unique<Webrtc::VideoTrack>(
Webrtc::VideoState::Active);
_videoLargeTrack = LargeTrack{
_videoLargeTrackWrap.get(),
endpoint.peer
};
addVideoOutput(endpoint.endpoint, { _videoLargeTrackWrap->sink() });
}, _lifetime);
updateInstanceMuteState();

View File

@ -297,11 +297,25 @@ public:
-> rpl::producer<VideoEndpoint> {
return _videoEndpointLarge.value();
}
[[nodiscard]] Webrtc::VideoTrack *videoLargeTrack() const {
struct LargeTrack {
Webrtc::VideoTrack *track = nullptr;
PeerData *peer = nullptr;
[[nodiscard]] explicit operator bool() const {
return (track != nullptr);
}
[[nodiscard]] bool operator==(LargeTrack other) const {
return (track == other.track) && (peer == other.peer);
}
[[nodiscard]] bool operator!=(LargeTrack other) const {
return !(*this == other);
}
};
[[nodiscard]] LargeTrack videoLargeTrack() const {
return _videoLargeTrack.current();
}
[[nodiscard]] auto videoLargeTrackValue() const
-> rpl::producer<Webrtc::VideoTrack*> {
-> rpl::producer<LargeTrack> {
return _videoLargeTrack.value();
}
[[nodiscard]] rpl::producer<Group::RejoinEvent> rejoinEvents() const {
@ -355,7 +369,6 @@ public:
private:
using GlobalShortcutValue = base::GlobalShortcutValue;
struct LargeTrack;
struct SinkPointer;
struct LoadingPart {
@ -524,8 +537,8 @@ private:
base::flat_map<std::string, EndpointType> _activeVideoEndpoints;
rpl::variable<VideoEndpoint> _videoEndpointLarge;
rpl::variable<bool> _videoEndpointPinned;
std::unique_ptr<LargeTrack> _videoLargeTrackWrap;
rpl::variable<Webrtc::VideoTrack*> _videoLargeTrack;
std::unique_ptr<Webrtc::VideoTrack> _videoLargeTrackWrap;
rpl::variable<LargeTrack> _videoLargeTrack;
base::flat_map<uint32, Data::LastSpokeTimes> _lastSpoke;
rpl::event_stream<Group::RejoinEvent> _rejoinEvents;
rpl::event_stream<> _allowedToSpeakNotifications;

View File

@ -0,0 +1,252 @@
/*
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_large_video.h"
#include "calls/group/calls_group_members_row.h"
#include "media/view/media_view_pip.h"
#include "webrtc/webrtc_video_track.h"
#include "ui/painter.h"
#include "styles/style_calls.h"
namespace Calls::Group {
namespace {
constexpr auto kShadowMaxAlpha = 80;
} // namespace
LargeVideo::LargeVideo(
QWidget *parent,
const style::GroupCallLargeVideo &st,
bool visible,
rpl::producer<LargeVideoTrack> track,
rpl::producer<bool> pinned)
: _content(parent, [=](QRect clip) { paint(clip); })
, _st(st)
, _pin(st::groupCallLargeVideoPin) {
_content.setVisible(visible);
setup(std::move(track), std::move(pinned));
}
void LargeVideo::raise() {
_content.raise();
}
void LargeVideo::setVisible(bool visible) {
_content.setVisible(visible);
}
void LargeVideo::setGeometry(int x, int y, int width, int height) {
_content.setGeometry(x, y, width, height);
}
void LargeVideo::setup(
rpl::producer<LargeVideoTrack> track,
rpl::producer<bool> pinned) {
_content.setAttribute(Qt::WA_OpaquePaintEvent);
std::move(pinned) | rpl::start_with_next([=](bool pinned) {
_pinned = pinned;
_content.update();
}, _content.lifetime());
rpl::combine(
_content.shownValue(),
std::move(track)
) | rpl::map([=](bool shown, LargeVideoTrack track) {
return shown ? track : LargeVideoTrack();
}) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](LargeVideoTrack track) {
_track = track;
_content.update();
_trackLifetime.destroy();
if (!track.track) {
return;
}
track.track->renderNextFrame(
) | rpl::start_with_next([=] {
const auto size = track.track->frameSize();
if (size.isEmpty()) {
track.track->markFrameShown();
}
_content.update();
}, _trackLifetime);
}, _content.lifetime());
}
void LargeVideo::paint(QRect clip) {
auto p = Painter(&_content);
const auto [image, rotation] = _track
? _track.track->frameOriginalWithRotation()
: std::pair<QImage, int>();
if (image.isNull()) {
p.fillRect(clip, Qt::black);
return;
}
auto hq = PainterHighQualityEnabler(p);
using namespace Media::View;
const auto size = _content.size();
const auto scaled = FlipSizeByRotation(
image.size(),
rotation
).scaled(size, Qt::KeepAspectRatio);
const auto left = (size.width() - scaled.width()) / 2;
const auto top = (size.height() - scaled.height()) / 2;
const auto target = QRect(QPoint(left, top), scaled);
if (UsePainterRotation(rotation)) {
if (rotation) {
p.save();
p.rotate(rotation);
}
p.drawImage(RotatedRect(target, rotation), image);
if (rotation) {
p.restore();
}
} else if (rotation) {
p.drawImage(target, RotateFrameImage(image, rotation));
} else {
p.drawImage(target, image);
}
_track.track->markFrameShown();
const auto fill = [&](QRect rect) {
if (rect.intersects(clip)) {
p.fillRect(rect.intersected(clip), Qt::black);
}
};
if (left > 0) {
fill({ 0, 0, left, size.height() });
}
if (const auto right = left + scaled.width()
; right < size.width()) {
fill({ right, 0, size.width() - right, size.height() });
}
if (top > 0) {
fill({ 0, 0, size.width(), top });
}
if (const auto bottom = top + scaled.height()
; bottom < size.height()) {
fill({ 0, bottom, size.width(), size.height() - bottom });
}
paintControls(p, clip);
}
void LargeVideo::paintControls(Painter &p, QRect clip) {
const auto width = _content.width();
const auto height = _content.height();
const auto topControls = (_st.controlsAlign == style::al_top);
if (_shadow.isNull()) {
if (topControls) {
_shadow = GenerateShadow(_st.shadowHeight, kShadowMaxAlpha, 0);
} else {
_shadow = GenerateShadow(_st.shadowHeight, 0, kShadowMaxAlpha);
}
}
const auto shadowRect = QRect(
0,
topControls ? 0 : (height - _st.shadowHeight),
width,
_st.shadowHeight);
const auto shadowFill = shadowRect.intersected(clip);
if (shadowFill.isEmpty()) {
return;
}
const auto factor = style::DevicePixelRatio();
p.drawImage(
shadowFill,
_shadow,
QRect(
0,
(shadowFill.y() - shadowRect.y()) * factor,
_shadow.width(),
shadowFill.height() * factor));
_track.row->lazyInitialize(st::groupCallMembersListItem);
p.setPen(topControls
? st::groupCallVideoTextFg
: st::groupCallVideoSubTextFg);
const auto hasWidth = width
- (topControls ? _st.pinPosition.x() : _st.iconPosition.x())
- _st.namePosition.x();
const auto nameLeft = _st.namePosition.x();
const auto nameTop = topControls
? _st.namePosition.y()
: (height - _st.namePosition.y());
_track.row->name().drawLeftElided(p, nameLeft, nameTop, hasWidth, width);
p.setPen(st::groupCallVideoSubTextFg);
const auto statusLeft = _st.statusPosition.x();
const auto statusTop = topControls
? _st.statusPosition.y()
: (height - _st.statusPosition.y());
_track.row->paintComplexStatusText(
p,
st::groupCallLargeVideoListItem,
statusLeft,
statusTop,
hasWidth,
width,
false,
MembersRowStyle::LargeVideo);
const auto &icon = st::groupCallLargeVideoCrossLine.icon;
const auto iconLeft = width - _st.iconPosition.x() - icon.width();
const auto iconTop = topControls
? _st.iconPosition.y()
: (height - _st.iconPosition.y());
_track.row->paintMuteIcon(
p,
{ iconLeft, iconTop, icon.width(), icon.height() },
MembersRowStyle::LargeVideo);
const auto pinWidth = st::groupCallLargeVideoPin.icon.width();
const auto pinLeft = topControls
? (width - _st.pinPosition.x() - pinWidth)
: _st.pinPosition.x();
const auto pinTop = topControls
? _st.pinPosition.y()
: (height - _st.pinPosition.y());
_pin.paint(p, pinLeft, pinTop, _pinned ? 1. : 0.);
}
QImage GenerateShadow(int height, int topAlpha, int bottomAlpha) {
Expects(topAlpha >= 0 && topAlpha < 256);
Expects(bottomAlpha >= 0 && bottomAlpha < 256);
Expects(height * style::DevicePixelRatio() < 65536);
auto result = QImage(
QSize(1, height * style::DevicePixelRatio()),
QImage::Format_ARGB32_Premultiplied);
if (topAlpha == bottomAlpha) {
result.fill(QColor(0, 0, 0, topAlpha));
return result;
}
constexpr auto kShift = 16;
constexpr auto kMultiply = (1U << kShift);
const auto values = std::abs(topAlpha - bottomAlpha);
const auto rows = uint32(result.height());
const auto step = (values * kMultiply) / (rows - 1);
const auto till = rows * uint32(step);
Assert(result.bytesPerLine() == sizeof(uint32));
auto ints = reinterpret_cast<uint32*>(result.bits());
if (topAlpha < bottomAlpha) {
for (auto i = uint32(0); i != till; i += step) {
*ints++ = ((topAlpha + (i >> kShift)) << 24);
}
} else {
for (auto i = uint32(0); i != till; i += step) {
*ints++ = ((topAlpha - (i >> kShift)) << 24);
}
}
return result;
}
} // namespace Calls::Group

View File

@ -0,0 +1,107 @@
/*
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 "ui/rp_widget.h"
#include "ui/effects/cross_line.h"
#if defined Q_OS_MAC
#define USE_OPENGL_LARGE_VIDEO
#endif // Q_OS_MAC
namespace style {
struct GroupCallLargeVideo;
} // namespace style
namespace Webrtc {
class VideoTrack;
} // namespace Webrtc
namespace Calls::Group {
class MembersRow;
struct LargeVideoTrack {
Webrtc::VideoTrack *track = nullptr;
MembersRow *row = nullptr;
[[nodiscard]] explicit operator bool() const {
return track != nullptr;
}
};
[[nodiscard]] inline bool operator==(
LargeVideoTrack a,
LargeVideoTrack b) noexcept {
return (a.track == b.track) && (a.row == b.row);
}
[[nodiscard]] inline bool operator!=(
LargeVideoTrack a,
LargeVideoTrack b) noexcept {
return !(a == b);
}
class LargeVideo final {
public:
LargeVideo(
QWidget *parent,
const style::GroupCallLargeVideo &st,
bool visible,
rpl::producer<LargeVideoTrack> track,
rpl::producer<bool> pinned);
void raise();
void setVisible(bool visible);
void setGeometry(int x, int y, int width, int height);
private:
#ifdef USE_OPENGL_LARGE_VIDEO
using ContentParent = Ui::RpWidgetWrap<QOpenGLWidget>;
#else // USE_OPENGL_OVERLAY_WIDGET
using ContentParent = Ui::RpWidget;
#endif // USE_OPENGL_OVERLAY_WIDGET
class Content final : public ContentParent {
public:
Content(QWidget *parent, Fn<void(QRect)> paint)
: ContentParent(parent), _paint(std::move(paint)) {
Expects(_paint != nullptr);
}
private:
void paintEvent(QPaintEvent *e) override {
_paint(e->rect());
}
Fn<void(QRect)> _paint;
};
void setup(
rpl::producer<LargeVideoTrack> track,
rpl::producer<bool> pinned);
void paint(QRect clip);
void paintControls(Painter &p, QRect clip);
Content _content;
const style::GroupCallLargeVideo &_st;
LargeVideoTrack _track;
QImage _shadow;
Ui::CrossLineAnimation _pin;
bool _pinned = false;
rpl::lifetime _trackLifetime;
};
[[nodiscard]] QImage GenerateShadow(
int height,
int topAlpha,
int bottomAlpha);
} // namespace Calls::Group

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/group/calls_group_menu.h"
#include "calls/group/calls_volume_item.h"
#include "calls/group/calls_group_members_row.h"
#include "calls/group/calls_group_large_video.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
@ -43,15 +44,17 @@ constexpr auto kShadowMaxAlpha = 74;
using Row = MembersRow;
class MembersController final
} // namespace
class Members::Controller final
: public PeerListController
, public MembersRowDelegate
, public base::has_weak_ptr {
public:
MembersController(
Controller(
not_null<GroupCall*> call,
not_null<QWidget*> menuParent);
~MembersController();
~Controller();
using MuteRequest = Group::MuteRequest;
using VolumeRequest = Group::VolumeRequest;
@ -73,6 +76,8 @@ public:
[[nodiscard]] auto kickParticipantRequests() const
-> rpl::producer<not_null<PeerData*>>;
Row *findRow(not_null<PeerData*> participantPeer) const;
bool rowIsMe(not_null<PeerData*> participantPeer) override;
bool rowCanMuteMembers() override;
void rowUpdateRow(not_null<Row*> row) override;
@ -145,7 +150,6 @@ private:
[[nodiscard]] bool allRowsAboveMoreImportantThanHand(
not_null<Row*> row,
uint64 raiseHandRating) const;
Row *findRow(not_null<PeerData*> participantPeer) const;
const Data::GroupCallParticipant *findParticipant(
const std::string &endpoint) const;
const std::string &computeScreenEndpoint(
@ -189,6 +193,7 @@ private:
Ui::CrossLineAnimation _inactiveNarrowCrossLine;
Ui::CrossLineAnimation _coloredNarrowCrossLine;
Ui::CrossLineAnimation _videoNarrowCrossLine;
Ui::CrossLineAnimation _videoLargeCrossLine;
Ui::RoundRect _narrowRoundRectSelected;
Ui::RoundRect _narrowRoundRect;
QImage _narrowShadow;
@ -197,7 +202,7 @@ private:
};
MembersController::MembersController(
Members::Controller::Controller(
not_null<GroupCall*> call,
not_null<QWidget*> menuParent)
: _call(call)
@ -209,6 +214,7 @@ MembersController::MembersController(
, _inactiveNarrowCrossLine(st::groupCallNarrowInactiveCrossLine)
, _coloredNarrowCrossLine(st::groupCallNarrowColoredCrossLine)
, _videoNarrowCrossLine(st::groupCallVideoCrossLine)
, _videoLargeCrossLine(st::groupCallLargeVideoCrossLine)
, _narrowRoundRectSelected(
ImageRoundRadius::Large,
st::groupCallMembersBgOver)
@ -267,11 +273,11 @@ MembersController::MembersController(
}, _lifetime);
}
MembersController::~MembersController() {
Members::Controller::~Controller() {
base::take(_menu);
}
void MembersController::setRowVideoEndpoint(
void Members::Controller::setRowVideoEndpoint(
not_null<Row*> row,
const std::string &endpoint) {
const auto was = row->videoTrackEndpoint();
@ -290,7 +296,7 @@ void MembersController::setRowVideoEndpoint(
}
}
void MembersController::setupListChangeViewers() {
void Members::Controller::setupListChangeViewers() {
_call->real(
) | rpl::start_with_next([=](not_null<Data::GroupCall*> real) {
subscribeToChanges(real);
@ -413,7 +419,7 @@ void MembersController::setupListChangeViewers() {
}, _lifetime);
}
void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
void Members::Controller::subscribeToChanges(not_null<Data::GroupCall*> real) {
_fullCount = real->fullCountValue();
real->participantsReloaded(
@ -449,30 +455,7 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
}
}
void MembersController::generateNarrowShadow() {
const auto factor = style::DevicePixelRatio();
_narrowShadow = QImage(
QSize(1, st::groupCallNarrowShadowHeight) * factor,
QImage::Format_ARGB32_Premultiplied);
const auto height = uint32(_narrowShadow.height());
constexpr auto kShift = 24;
constexpr auto kMultiply = (1U << kShift);
const auto step = 1 + ((kShadowMaxAlpha * kMultiply) / (height - 1));
const auto till = height * uint32(step);
auto ints = reinterpret_cast<uint32*>(_narrowShadow.bits());
const auto perline = _narrowShadow.bytesPerLine() / sizeof(uint32);
for (auto i = uint32(0); i != till; i += step) {
const auto alpha = (i >> kShift);
const auto color = alpha << 24;
ranges::fill(
gsl::span{ ints, size_t(_narrowShadow.width()) },
color);
ints += perline;
LOG(("ALPHA: %1").arg(alpha));
}
}
void MembersController::appendInvitedUsers() {
void Members::Controller::appendInvitedUsers() {
if (const auto id = _call->id()) {
for (const auto user : _peer->owner().invitedToCallUsers(id)) {
if (auto row = createInvitedRow(user)) {
@ -494,7 +477,7 @@ void MembersController::appendInvitedUsers() {
}, _lifetime);
}
void MembersController::updateRow(
void Members::Controller::updateRow(
const std::optional<Data::GroupCallParticipant> &was,
const Data::GroupCallParticipant &now) {
auto reorderIfInvitedBefore = 0;
@ -561,7 +544,7 @@ void MembersController::updateRow(
}
}
bool MembersController::allRowsAboveAreSpeaking(not_null<Row*> row) const {
bool Members::Controller::allRowsAboveAreSpeaking(not_null<Row*> row) const {
const auto count = delegate()->peerListFullRowsCount();
for (auto i = 0; i != count; ++i) {
const auto above = delegate()->peerListRowAt(i);
@ -575,7 +558,7 @@ bool MembersController::allRowsAboveAreSpeaking(not_null<Row*> row) const {
return false;
}
bool MembersController::allRowsAboveMoreImportantThanHand(
bool Members::Controller::allRowsAboveMoreImportantThanHand(
not_null<Row*> row,
uint64 raiseHandRating) const {
Expects(raiseHandRating > 0);
@ -598,7 +581,7 @@ bool MembersController::allRowsAboveMoreImportantThanHand(
return false;
}
bool MembersController::needToReorder(not_null<Row*> row) const {
bool Members::Controller::needToReorder(not_null<Row*> row) const {
// All reorder cases:
// - bring speaking up
// - bring raised hand up
@ -634,7 +617,7 @@ bool MembersController::needToReorder(not_null<Row*> row) const {
return false;
}
void MembersController::checkRowPosition(not_null<Row*> row) {
void Members::Controller::checkRowPosition(not_null<Row*> row) {
if (_menu) {
// Don't reorder rows while we show the popup menu.
_menuCheckRowsAfterHidden.emplace(row->peer());
@ -680,7 +663,7 @@ void MembersController::checkRowPosition(not_null<Row*> row) {
: makeComparator(projForOther));
}
void MembersController::updateRow(
void Members::Controller::updateRow(
not_null<Row*> row,
const Data::GroupCallParticipant *participant) {
const auto wasSounding = row->sounding();
@ -717,12 +700,12 @@ void MembersController::updateRow(
delegate()->peerListUpdateRow(row);
}
void MembersController::removeRow(not_null<Row*> row) {
void Members::Controller::removeRow(not_null<Row*> row) {
_soundingRowBySsrc.remove(row->ssrc());
delegate()->peerListRemoveRow(row);
}
void MembersController::updateRowLevel(
void Members::Controller::updateRowLevel(
not_null<Row*> row,
float level) {
if (_skipRowLevelUpdate) {
@ -731,12 +714,13 @@ void MembersController::updateRowLevel(
row->updateLevel(level);
}
Row *MembersController::findRow(not_null<PeerData*> participantPeer) const {
Row *Members::Controller::findRow(
not_null<PeerData*> participantPeer) const {
return static_cast<Row*>(
delegate()->peerListFindRow(participantPeer->id.value));
}
const Data::GroupCallParticipant *MembersController::findParticipant(
const Data::GroupCallParticipant *Members::Controller::findParticipant(
const std::string &endpoint) const {
if (endpoint.empty()) {
return nullptr;
@ -757,25 +741,25 @@ const Data::GroupCallParticipant *MembersController::findParticipant(
}
}
const std::string &MembersController::computeScreenEndpoint(
const std::string &Members::Controller::computeScreenEndpoint(
not_null<const Data::GroupCallParticipant*> participant) const {
return (participant->peer == _call->joinAs())
? _call->screenSharingEndpoint()
: participant->screenEndpoint();
}
const std::string &MembersController::computeCameraEndpoint(
const std::string &Members::Controller::computeCameraEndpoint(
not_null<const Data::GroupCallParticipant*> participant) const {
return (participant->peer == _call->joinAs())
? _call->cameraSharingEndpoint()
: participant->cameraEndpoint();
}
Main::Session &MembersController::session() const {
Main::Session &Members::Controller::session() const {
return _call->peer()->session();
}
void MembersController::prepare() {
void Members::Controller::prepare() {
delegate()->peerListSetSearchMode(PeerListSearchMode::Disabled);
//delegate()->peerListSetTitle(std::move(title));
setDescriptionText(tr::lng_contacts_loading(tr::now));
@ -793,11 +777,11 @@ void MembersController::prepare() {
_prepared = true;
}
bool MembersController::isMe(not_null<PeerData*> participantPeer) const {
bool Members::Controller::isMe(not_null<PeerData*> participantPeer) const {
return (_call->joinAs() == participantPeer);
}
void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
void Members::Controller::prepareRows(not_null<Data::GroupCall*> real) {
auto foundMe = false;
auto changed = false;
const auto &participants = real->participants();
@ -847,35 +831,35 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
}
}
void MembersController::loadMoreRows() {
void Members::Controller::loadMoreRows() {
if (const auto real = _call->lookupReal()) {
real->requestParticipants();
}
}
auto MembersController::toggleMuteRequests() const
auto Members::Controller::toggleMuteRequests() const
-> rpl::producer<MuteRequest> {
return _toggleMuteRequests.events();
}
auto MembersController::changeVolumeRequests() const
auto Members::Controller::changeVolumeRequests() const
-> rpl::producer<VolumeRequest> {
return _changeVolumeRequests.events();
}
bool MembersController::rowIsMe(not_null<PeerData*> participantPeer) {
bool Members::Controller::rowIsMe(not_null<PeerData*> participantPeer) {
return isMe(participantPeer);
}
bool MembersController::rowCanMuteMembers() {
bool Members::Controller::rowCanMuteMembers() {
return _peer->canManageGroupCall();
}
void MembersController::rowUpdateRow(not_null<Row*> row) {
void Members::Controller::rowUpdateRow(not_null<Row*> row) {
delegate()->peerListUpdateRow(row);
}
void MembersController::rowScheduleRaisedHandStatusRemove(
void Members::Controller::rowScheduleRaisedHandStatusRemove(
not_null<Row*> row) {
const auto id = row->id();
const auto when = crl::now() + kKeepRaisedHandStatusDuration;
@ -888,7 +872,7 @@ void MembersController::rowScheduleRaisedHandStatusRemove(
scheduleRaisedHandStatusRemove();
}
void MembersController::scheduleRaisedHandStatusRemove() {
void Members::Controller::scheduleRaisedHandStatusRemove() {
auto waiting = crl::time(0);
const auto now = crl::now();
for (auto i = begin(_raisedHandStatusRemoveAt)
@ -913,13 +897,16 @@ void MembersController::scheduleRaisedHandStatusRemove() {
}
}
void MembersController::rowPaintIcon(
void Members::Controller::rowPaintIcon(
Painter &p,
QRect rect,
const IconState &state) {
const auto narrowUserpic = (state.narrowStyle == NarrowStyle::Userpic);
const auto narrowVideo = (state.narrowStyle == NarrowStyle::Video);
const auto &greenIcon = narrowVideo
const auto narrowUserpic = (state.style == MembersRowStyle::Userpic);
const auto narrowVideo = (state.style == MembersRowStyle::Video);
const auto largeVideo = (state.style == MembersRowStyle::LargeVideo);
const auto &greenIcon = largeVideo
? st::groupCallLargeVideoCrossLine.icon
: narrowVideo
? st::groupCallVideoCrossLine.icon
: narrowUserpic
? st::groupCallNarrowColoredCrossLine.icon
@ -933,7 +920,9 @@ void MembersController::rowPaintIcon(
} else if (state.speaking == 0.) {
if (state.active == 1.) {
// Just gray icon, no cross, no coloring.
const auto &grayIcon = narrowVideo
const auto &grayIcon = largeVideo
? st::groupCallLargeVideoCrossLine.icon
: narrowVideo
? st::groupCallVideoCrossLine.icon
: narrowUserpic
? st::groupCallNarrowInactiveCrossLine.icon
@ -948,12 +937,14 @@ void MembersController::rowPaintIcon(
return;
}
// Red crossed icon, colorized once, cached as last frame.
auto &line = narrowVideo
auto &line = largeVideo
? _videoLargeCrossLine
: narrowVideo
? _videoNarrowCrossLine
: narrowUserpic
? _coloredNarrowCrossLine
: _coloredCrossLine;
const auto color = narrowVideo
const auto color = (largeVideo || narrowVideo)
? std::nullopt
: std::make_optional(st::groupCallMemberMutedIcon->c);
line.paint(
@ -965,7 +956,9 @@ void MembersController::rowPaintIcon(
return;
} else if (state.muted == 0.) {
// Gray crossed icon, no coloring, cached as last frame.
auto &line = narrowVideo
auto &line = largeVideo
? _videoLargeCrossLine
: narrowVideo
? _videoNarrowCrossLine
: narrowUserpic
? _inactiveNarrowCrossLine
@ -985,14 +978,16 @@ void MembersController::rowPaintIcon(
activeInactiveColor,
st::groupCallMemberMutedIcon,
state.muted);
const auto color = narrowVideo
const auto color = (largeVideo || narrowVideo)
? std::nullopt
: std::make_optional(iconColor);
// Don't use caching of the last frame,
// because 'muted' may animate color.
const auto crossProgress = std::min(1. - state.active, 0.9999);
auto &line = narrowVideo
auto &line = largeVideo
? _videoLargeCrossLine
: narrowVideo
? _videoNarrowCrossLine
: narrowUserpic
? _inactiveNarrowCrossLine
@ -1000,7 +995,7 @@ void MembersController::rowPaintIcon(
line.paint(p, left, top, crossProgress, color);
}
void MembersController::rowPaintNarrowBackground(
void Members::Controller::rowPaintNarrowBackground(
Painter &p,
int x,
int y,
@ -1010,7 +1005,7 @@ void MembersController::rowPaintNarrowBackground(
{ QPoint(x, y), st::groupCallNarrowSize });
}
void MembersController::rowPaintNarrowBorder(
void Members::Controller::rowPaintNarrowBorder(
Painter &p,
int x,
int y,
@ -1029,14 +1024,17 @@ void MembersController::rowPaintNarrowBorder(
st::roundRadiusLarge);
}
void MembersController::rowPaintNarrowShadow(
void Members::Controller::rowPaintNarrowShadow(
Painter &p,
int x,
int y,
int sizew,
int sizeh) {
if (_narrowShadow.isNull()) {
generateNarrowShadow();
_narrowShadow = GenerateShadow(
st::groupCallNarrowShadowHeight,
0,
kShadowMaxAlpha);
}
const auto height = st::groupCallNarrowShadowHeight;
p.drawImage(
@ -1044,11 +1042,11 @@ void MembersController::rowPaintNarrowShadow(
_narrowShadow);
}
int MembersController::customRowHeight() {
int Members::Controller::customRowHeight() {
return st::groupCallNarrowSize.height() + st::groupCallNarrowRowSkip * 2;
}
void MembersController::customRowPaint(
void Members::Controller::customRowPaint(
Painter &p,
crl::time now,
not_null<PeerListRow*> row,
@ -1067,7 +1065,7 @@ void MembersController::customRowPaint(
selected);
}
bool MembersController::customRowSelectionPoint(
bool Members::Controller::customRowSelectionPoint(
not_null<PeerListRow*> row,
int x,
int y) {
@ -1077,7 +1075,7 @@ bool MembersController::customRowSelectionPoint(
&& y < st::groupCallNarrowRowSkip + st::groupCallNarrowSize.height();
}
Fn<QImage()> MembersController::customRowRippleMaskGenerator() {
Fn<QImage()> Members::Controller::customRowRippleMaskGenerator() {
return [] {
return Ui::RippleAnimation::roundRectMask(
st::groupCallNarrowSize,
@ -1085,12 +1083,12 @@ Fn<QImage()> MembersController::customRowRippleMaskGenerator() {
};
}
auto MembersController::kickParticipantRequests() const
auto Members::Controller::kickParticipantRequests() const
-> rpl::producer<not_null<PeerData*>>{
return _kickParticipantRequests.events();
}
void MembersController::rowClicked(not_null<PeerListRow*> row) {
void Members::Controller::rowClicked(not_null<PeerListRow*> row) {
delegate()->peerListShowRowMenu(row, [=](not_null<Ui::PopupMenu*> menu) {
if (!_menu || _menu.get() != menu) {
return;
@ -1105,12 +1103,12 @@ void MembersController::rowClicked(not_null<PeerListRow*> row) {
});
}
void MembersController::rowActionClicked(
void Members::Controller::rowActionClicked(
not_null<PeerListRow*> row) {
rowClicked(row);
}
base::unique_qptr<Ui::PopupMenu> MembersController::rowContextMenu(
base::unique_qptr<Ui::PopupMenu> Members::Controller::rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) {
auto result = createRowContextMenu(parent, row);
@ -1127,7 +1125,7 @@ base::unique_qptr<Ui::PopupMenu> MembersController::rowContextMenu(
return result;
}
base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) {
const auto participantPeer = row->peer();
@ -1278,7 +1276,7 @@ base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
return result;
}
void MembersController::addMuteActionsToContextMenu(
void Members::Controller::addMuteActionsToContextMenu(
not_null<Ui::PopupMenu*> menu,
not_null<PeerData*> participantPeer,
bool participantIsCallAdmin,
@ -1399,20 +1397,20 @@ void MembersController::addMuteActionsToContextMenu(
}
}
std::unique_ptr<Row> MembersController::createRowForMe() {
std::unique_ptr<Row> Members::Controller::createRowForMe() {
auto result = std::make_unique<Row>(this, _call->joinAs());
updateRow(result.get(), nullptr);
return result;
}
std::unique_ptr<Row> MembersController::createRow(
std::unique_ptr<Row> Members::Controller::createRow(
const Data::GroupCallParticipant &participant) {
auto result = std::make_unique<Row>(this, participant.peer);
updateRow(result.get(), &participant);
return result;
}
std::unique_ptr<Row> MembersController::createInvitedRow(
std::unique_ptr<Row> Members::Controller::createInvitedRow(
not_null<PeerData*> participantPeer) {
if (findRow(participantPeer)) {
return nullptr;
@ -1422,15 +1420,13 @@ std::unique_ptr<Row> MembersController::createInvitedRow(
return result;
}
} // namespace
Members::Members(
not_null<QWidget*> parent,
not_null<GroupCall*> call)
: RpWidget(parent)
, _call(call)
, _scroll(this)
, _listController(std::make_unique<MembersController>(call, parent))
, _listController(std::make_unique<Controller>(call, parent))
, _layout(_scroll->setOwnedWidget(
object_ptr<Ui::VerticalLayout>(_scroll.data())))
, _pinnedVideo(_layout->add(object_ptr<Ui::RpWidget>(_layout.get()))) {
@ -1442,22 +1438,21 @@ Members::Members(
_listController->setDelegate(static_cast<PeerListDelegate*>(this));
}
Members::~Members() = default;
auto Members::toggleMuteRequests() const
-> rpl::producer<Group::MuteRequest> {
return static_cast<MembersController*>(
_listController.get())->toggleMuteRequests();
return _listController->toggleMuteRequests();
}
auto Members::changeVolumeRequests() const
-> rpl::producer<Group::VolumeRequest> {
return static_cast<MembersController*>(
_listController.get())->changeVolumeRequests();
return _listController->changeVolumeRequests();
}
auto Members::kickParticipantRequests() const
-> rpl::producer<not_null<PeerData*>> {
return static_cast<MembersController*>(
_listController.get())->kickParticipantRequests();
return _listController->kickParticipantRequests();
}
int Members::desiredHeight() const {
@ -1480,12 +1475,10 @@ int Members::desiredHeight() const {
}
rpl::producer<int> Members::desiredHeightValue() const {
const auto controller = static_cast<MembersController*>(
_listController.get());
return rpl::combine(
heightValue(),
_addMemberButton.value(),
controller->fullCountValue()
_listController->fullCountValue()
) | rpl::map([=] {
return desiredHeight();
});
@ -1572,6 +1565,10 @@ void Members::setupAddMember(not_null<GroupCall*> call) {
}, lifetime());
}
Row *Members::lookupRow(not_null<PeerData*> peer) const {
return _listController->findRow(peer);
}
void Members::setMode(PanelMode mode) {
if (_mode.current() == mode) {
return;
@ -1583,8 +1580,7 @@ void Members::setMode(PanelMode mode) {
}
rpl::producer<int> Members::fullCountValue() const {
return static_cast<MembersController*>(
_listController.get())->fullCountValue();
return _listController->fullCountValue();
}
void Members::setupList() {
@ -1624,8 +1620,8 @@ void Members::setupPinnedVideo() {
rpl::combine(
_mode.value(),
_call->videoLargeTrackValue()
) | rpl::map([](PanelMode mode, Webrtc::VideoTrack *track) {
return (mode == PanelMode::Default) ? track : nullptr;
) | rpl::map([](PanelMode mode, GroupCall::LargeTrack track) {
return (mode == PanelMode::Default) ? track.track : nullptr;
}) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](Webrtc::VideoTrack *track) {
_pinnedTrackLifetime.destroy();

View File

@ -26,6 +26,7 @@ class GroupCall;
namespace Calls::Group {
class MembersRow;
struct VolumeRequest;
struct MuteRequest;
enum class PanelMode;
@ -37,6 +38,7 @@ public:
Members(
not_null<QWidget*> parent,
not_null<GroupCall*> call);
~Members();
[[nodiscard]] int desiredHeight() const;
[[nodiscard]] rpl::producer<int> desiredHeightValue() const override;
@ -51,9 +53,12 @@ public:
return _addMemberRequests.events();
}
[[nodiscard]] MembersRow *lookupRow(not_null<PeerData*> peer) const;
void setMode(PanelMode mode);
private:
class Controller;
using ListWidget = PeerListContent;
void resizeEvent(QResizeEvent *e) override;
@ -84,7 +89,7 @@ private:
const not_null<GroupCall*> _call;
rpl::variable<PanelMode> _mode = PanelMode();
object_ptr<Ui::ScrollArea> _scroll;
std::unique_ptr<PeerListController> _listController;
std::unique_ptr<Controller> _listController;
not_null<Ui::VerticalLayout*> _layout;
const not_null<Ui::RpWidget*> _pinnedVideo;
rpl::variable<Ui::RpWidget*> _addMemberButton = nullptr;

View File

@ -495,20 +495,27 @@ void MembersRow::paintScaledUserpic(
_blobsAnimation->userpicCache);
}
void MembersRow::paintMuteIcon(
Painter &p,
QRect iconRect,
MembersRowStyle style) {
_delegate->rowPaintIcon(p, iconRect, computeIconState(style));
}
void MembersRow::paintNarrowName(
Painter &p,
int x,
int y,
int sizew,
int sizeh,
NarrowStyle style) {
MembersRowStyle style) {
if (_narrowName.isEmpty()) {
_narrowName.setText(
st::semiboldTextStyle,
generateShortName(),
Ui::NameTextOptions());
}
if (style == NarrowStyle::Video) {
if (style == MembersRowStyle::Video) {
_delegate->rowPaintNarrowShadow(p, x, y, sizew, sizeh);
}
const auto &icon = st::groupCallVideoCrossLine.icon;
@ -525,7 +532,7 @@ void MembersRow::paintNarrowName(
_delegate->rowPaintIcon(p, iconRect, state);
p.setPen([&] {
if (style == NarrowStyle::Video) {
if (style == MembersRowStyle::Video) {
return st::groupCallVideoTextFg->p;
} else if (state.speaking == 1. && !state.mutedByMe) {
return st::groupCallMemberActiveIcon->p;
@ -580,7 +587,7 @@ void MembersRow::paintComplexUserpic(
bool selected) {
if (mode == PanelMode::Wide) {
if (paintVideo(p, x, y, sizew, sizeh, mode)) {
paintNarrowName(p, x, y, sizew, sizeh, NarrowStyle::Video);
paintNarrowName(p, x, y, sizew, sizeh, MembersRowStyle::Video);
_delegate->rowPaintNarrowBorder(p, x, y, this);
return;
}
@ -602,7 +609,7 @@ void MembersRow::paintComplexUserpic(
sizeh,
mode);
if (mode == PanelMode::Wide) {
paintNarrowName(p, x, y, sizew, sizeh, NarrowStyle::Userpic);
paintNarrowName(p, x, y, sizew, sizeh, MembersRowStyle::Userpic);
_delegate->rowPaintNarrowBorder(p, x, y, this);
}
}
@ -699,6 +706,26 @@ void MembersRow::paintStatusText(
int availableWidth,
int outerWidth,
bool selected) {
paintComplexStatusText(
p,
st,
x,
y,
availableWidth,
outerWidth,
selected,
MembersRowStyle::None);
}
void MembersRow::paintComplexStatusText(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int availableWidth,
int outerWidth,
bool selected,
MembersRowStyle style) {
const auto &font = st::normalFont;
const auto about = (_state == State::Inactive
|| _state == State::Muted
@ -727,7 +754,9 @@ void MembersRow::paintStatusText(
return;
}
p.setFont(font);
if (_state == State::MutedByMe) {
if (style == MembersRowStyle::LargeVideo) {
p.setPen(st::groupCallVideoSubTextFg);
} else if (_state == State::MutedByMe) {
p.setPen(st::groupCallMemberMutedIcon);
} else {
p.setPen(st::groupCallMemberNotJoinedStatus);
@ -738,7 +767,7 @@ void MembersRow::paintStatusText(
outerWidth,
(_state == State::MutedByMe
? tr::lng_group_call_muted_by_me_status(tr::now)
: !about.isEmpty()
: (!about.isEmpty() && style != MembersRowStyle::LargeVideo)
? font->m.elidedText(about, Qt::ElideRight, availableWidth)
: _delegate->rowIsMe(peer())
? tr::lng_status_connecting(tr::now)
@ -797,11 +826,11 @@ void MembersRow::paintAction(
_actionRipple.reset();
}
}
_delegate->rowPaintIcon(p, iconRect, computeIconState());
paintMuteIcon(p, iconRect);
}
MembersRowDelegate::IconState MembersRow::computeIconState(
NarrowStyle style) const {
MembersRowStyle style) const {
const auto speaking = _speakingAnimation.value(_speaking ? 1. : 0.);
const auto active = _activeAnimation.value(
(_state == State::Active) ? 1. : 0.);
@ -814,7 +843,7 @@ MembersRowDelegate::IconState MembersRow::computeIconState(
.muted = muted,
.mutedByMe = (_state == State::MutedByMe),
.raisedHand = (_state == State::RaisedHand),
.narrowStyle = style,
.style = style,
};
}

View File

@ -27,10 +27,11 @@ class RippleAnimation;
namespace Calls::Group {
enum class NarrowStyle {
enum class MembersRowStyle {
None,
Userpic,
Video,
LargeVideo,
};
class MembersRow;
@ -42,7 +43,7 @@ public:
float64 muted = 0.;
bool mutedByMe = false;
bool raisedHand = false;
NarrowStyle narrowStyle = NarrowStyle::None;
MembersRowStyle style = MembersRowStyle::None;
};
virtual bool rowIsMe(not_null<PeerData*> participantPeer) = 0;
virtual bool rowCanMuteMembers() = 0;
@ -155,6 +156,19 @@ public:
int availableWidth,
int outerWidth,
bool selected) override;
void paintComplexStatusText(
Painter &p,
const style::PeerListItem &st,
int x,
int y,
int availableWidth,
int outerWidth,
bool selected,
MembersRowStyle style);
void paintMuteIcon(
Painter &p,
QRect iconRect,
MembersRowStyle style = MembersRowStyle::None);
private:
struct BlobsAnimation;
@ -211,9 +225,9 @@ private:
int y,
int sizew,
int sizeh,
NarrowStyle style);
MembersRowStyle style);
[[nodiscard]] MembersRowDelegate::IconState computeIconState(
NarrowStyle style = NarrowStyle::None) const;
MembersRowStyle style = MembersRowStyle::None) const;
const not_null<MembersRowDelegate*> _delegate;
State _state = State::Inactive;

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/group/calls_group_members.h"
#include "calls/group/calls_group_settings.h"
#include "calls/group/calls_group_menu.h"
#include "calls/group/calls_group_large_video.h"
#include "calls/group/ui/desktop_capture_choose_source.h"
#include "ui/platform/ui_platform_window_title.h"
#include "ui/platform/ui_platform_utility.h"
@ -48,7 +49,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer_rpl.h"
#include "app.h"
#include "apiwrap.h" // api().kickParticipant.
#include "media/view/media_view_pip.h"
#include "webrtc/webrtc_video_track.h"
#include "styles/style_calls.h"
#include "styles/style_layers.h"
@ -994,89 +994,24 @@ void Panel::raiseControls() {
}
void Panel::setupPinnedVideo() {
_pinnedVideo.create(widget());
_pinnedVideo->setVisible(_mode == PanelMode::Wide);
_pinnedVideo->setAttribute(Qt::WA_OpaquePaintEvent, true);
auto track = _call->videoLargeTrackValue(
) | rpl::map([=](GroupCall::LargeTrack track) {
const auto row = track ? _members->lookupRow(track.peer) : nullptr;
Assert(!track || row != nullptr);
return LargeVideoTrack{
row ? track.track : nullptr,
row
};
});
const auto visible = (_mode == PanelMode::Wide);
_pinnedVideo = std::make_unique<LargeVideo>(
widget(),
st::groupCallLargeVideoWide,
visible,
std::move(track),
_call->videoEndpointPinnedValue());
raiseControls();
rpl::combine(
_pinnedVideo->shownValue(),
_call->videoLargeTrackValue()
) | rpl::map([](bool shown, Webrtc::VideoTrack *track) {
return shown ? track : nullptr;
}) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](Webrtc::VideoTrack *track) {
_pinnedTrackLifetime.destroy();
if (!track) {
_pinnedVideo->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
QPainter(_pinnedVideo.data()).fillRect(clip, Qt::black);
}, _pinnedTrackLifetime);
_pinnedVideo->update();
return;
}
track->renderNextFrame(
) | rpl::start_with_next([=] {
const auto size = track->frameSize();
if (size.isEmpty()) {
track->markFrameShown();
} else {
_pinnedVideo->update();
}
}, _pinnedTrackLifetime);
_pinnedVideo->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
const auto [image, rotation]
= track->frameOriginalWithRotation();
if (image.isNull()) {
return;
}
auto p = QPainter(_pinnedVideo);
auto hq = PainterHighQualityEnabler(p);
using namespace Media::View;
const auto size = _pinnedVideo->size();
const auto scaled = FlipSizeByRotation(
image.size(),
rotation
).scaled(size, Qt::KeepAspectRatio);
const auto left = (size.width() - scaled.width()) / 2;
const auto top = (size.height() - scaled.height()) / 2;
const auto target = QRect(QPoint(left, top), scaled);
if (UsePainterRotation(rotation)) {
if (rotation) {
p.save();
p.rotate(rotation);
}
p.drawImage(RotatedRect(target, rotation), image);
if (rotation) {
p.restore();
}
} else if (rotation) {
p.drawImage(target, RotateFrameImage(image, rotation));
} else {
p.drawImage(target, image);
}
if (left > 0) {
p.fillRect(0, 0, left, size.height(), Qt::black);
}
if (const auto right = left + scaled.width()
; right < size.width()) {
const auto fill = size.width() - right;
p.fillRect(right, 0, fill, size.height(), Qt::black);
}
if (top > 0) {
p.fillRect(0, 0, size.width(), top, Qt::black);
}
if (const auto bottom = top + scaled.height()
; bottom < size.height()) {
const auto fill = size.height() - bottom;
p.fillRect(0, bottom, size.width(), fill, Qt::black);
}
track->markFrameShown();
}, _pinnedTrackLifetime);
}, widget()->lifetime());
}
void Panel::setupJoinAsChangedToasts() {

View File

@ -54,6 +54,7 @@ struct CallBodyLayout;
namespace Calls::Group {
class Members;
class LargeVideo;
enum class PanelMode;
class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate {
@ -141,7 +142,7 @@ private:
object_ptr<Ui::DropdownMenu> _menu = { nullptr };
object_ptr<Ui::AbstractButton> _joinAsToggle = { nullptr };
object_ptr<Members> _members = { nullptr };
object_ptr<Ui::RpWidget> _pinnedVideo = { nullptr };
std::unique_ptr<LargeVideo> _pinnedVideo;
rpl::lifetime _pinnedTrackLifetime;
object_ptr<Ui::FlatLabel> _startsIn = { nullptr };
object_ptr<Ui::RpWidget> _countdown = { nullptr };

@ -1 +1 @@
Subproject commit 445d42bacf80d8c7fa2a9d2ff790341cb656ff25
Subproject commit 63dcf796d7721e02ee179c61aec7045b2699a3eb

@ -1 +1 @@
Subproject commit 86ca2dd27e52fa929423b61cd7861a0bc9483e28
Subproject commit 0c867b0b6ae29e6ae5d5c2fda3824cdb595900eb