diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 257a05ca29..f6e7bd8015 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -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 diff --git a/Telegram/Resources/icons/calls/voice_enlarge.png b/Telegram/Resources/icons/calls/voice_enlarge.png new file mode 100644 index 0000000000..1990011322 Binary files /dev/null and b/Telegram/Resources/icons/calls/voice_enlarge.png differ diff --git a/Telegram/Resources/icons/calls/voice_enlarge@2x.png b/Telegram/Resources/icons/calls/voice_enlarge@2x.png new file mode 100644 index 0000000000..3c23f91e9a Binary files /dev/null and b/Telegram/Resources/icons/calls/voice_enlarge@2x.png differ diff --git a/Telegram/Resources/icons/calls/voice_enlarge@3x.png b/Telegram/Resources/icons/calls/voice_enlarge@3x.png new file mode 100644 index 0000000000..dd75b153a5 Binary files /dev/null and b/Telegram/Resources/icons/calls/voice_enlarge@3x.png differ diff --git a/Telegram/Resources/icons/calls/voice_minimize.png b/Telegram/Resources/icons/calls/voice_minimize.png new file mode 100644 index 0000000000..85c36639f6 Binary files /dev/null and b/Telegram/Resources/icons/calls/voice_minimize.png differ diff --git a/Telegram/Resources/icons/calls/voice_minimize@2x.png b/Telegram/Resources/icons/calls/voice_minimize@2x.png new file mode 100644 index 0000000000..0705454d8e Binary files /dev/null and b/Telegram/Resources/icons/calls/voice_minimize@2x.png differ diff --git a/Telegram/Resources/icons/calls/voice_minimize@3x.png b/Telegram/Resources/icons/calls/voice_minimize@3x.png new file mode 100644 index 0000000000..65870ecdd3 Binary files /dev/null and b/Telegram/Resources/icons/calls/voice_minimize@3x.png differ diff --git a/Telegram/Resources/icons/calls/voice_pin.png b/Telegram/Resources/icons/calls/voice_pin.png new file mode 100644 index 0000000000..4866e5f92b Binary files /dev/null and b/Telegram/Resources/icons/calls/voice_pin.png differ diff --git a/Telegram/Resources/icons/calls/voice_pin@2x.png b/Telegram/Resources/icons/calls/voice_pin@2x.png new file mode 100644 index 0000000000..bbcb5fccf9 Binary files /dev/null and b/Telegram/Resources/icons/calls/voice_pin@2x.png differ diff --git a/Telegram/Resources/icons/calls/voice_pin@3x.png b/Telegram/Resources/icons/calls/voice_pin@3x.png new file mode 100644 index 0000000000..79ce77dd01 Binary files /dev/null and b/Telegram/Resources/icons/calls/voice_pin@3x.png differ diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 8d80846366..453186d9d9 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -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; +} diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index adaa9813c7..04b68dd8c1 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -170,14 +170,6 @@ private: }; -struct GroupCall::LargeTrack { - LargeTrack() : track(Webrtc::VideoState::Active) { - } - - Webrtc::VideoTrack track; - std::shared_ptr sink; -}; - struct GroupCall::SinkPointer { std::shared_ptr 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(); - _videoLargeTrack = &_videoLargeTrackWrap->track; - } - _videoLargeTrackWrap->sink = Webrtc::CreateProxySink( - _videoLargeTrackWrap->track.sink()); - addVideoOutput(endpoint.endpoint, { _videoLargeTrackWrap->sink }); + _videoLargeTrackWrap = std::make_unique( + Webrtc::VideoState::Active); + _videoLargeTrack = LargeTrack{ + _videoLargeTrackWrap.get(), + endpoint.peer + }; + addVideoOutput(endpoint.endpoint, { _videoLargeTrackWrap->sink() }); }, _lifetime); updateInstanceMuteState(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index aa5c19c416..03ddc97af7 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -297,11 +297,25 @@ public: -> rpl::producer { 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 { + -> rpl::producer { return _videoLargeTrack.value(); } [[nodiscard]] rpl::producer 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 _activeVideoEndpoints; rpl::variable _videoEndpointLarge; rpl::variable _videoEndpointPinned; - std::unique_ptr _videoLargeTrackWrap; - rpl::variable _videoLargeTrack; + std::unique_ptr _videoLargeTrackWrap; + rpl::variable _videoLargeTrack; base::flat_map _lastSpoke; rpl::event_stream _rejoinEvents; rpl::event_stream<> _allowedToSpeakNotifications; diff --git a/Telegram/SourceFiles/calls/group/calls_group_large_video.cpp b/Telegram/SourceFiles/calls/group/calls_group_large_video.cpp new file mode 100644 index 0000000000..9380c41c38 --- /dev/null +++ b/Telegram/SourceFiles/calls/group/calls_group_large_video.cpp @@ -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 track, + rpl::producer 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 track, + rpl::producer 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(); + 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(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 diff --git a/Telegram/SourceFiles/calls/group/calls_group_large_video.h b/Telegram/SourceFiles/calls/group/calls_group_large_video.h new file mode 100644 index 0000000000..551e45c7e0 --- /dev/null +++ b/Telegram/SourceFiles/calls/group/calls_group_large_video.h @@ -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 track, + rpl::producer 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; +#else // USE_OPENGL_OVERLAY_WIDGET + using ContentParent = Ui::RpWidget; +#endif // USE_OPENGL_OVERLAY_WIDGET + + class Content final : public ContentParent { + public: + Content(QWidget *parent, Fn paint) + : ContentParent(parent), _paint(std::move(paint)) { + Expects(_paint != nullptr); + } + + private: + void paintEvent(QPaintEvent *e) override { + _paint(e->rect()); + } + + Fn _paint; + + }; + + void setup( + rpl::producer track, + rpl::producer 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 diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 0ed4d35e58..5dd8688d22 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -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 call, not_null menuParent); - ~MembersController(); + ~Controller(); using MuteRequest = Group::MuteRequest; using VolumeRequest = Group::VolumeRequest; @@ -73,6 +76,8 @@ public: [[nodiscard]] auto kickParticipantRequests() const -> rpl::producer>; + Row *findRow(not_null participantPeer) const; + bool rowIsMe(not_null participantPeer) override; bool rowCanMuteMembers() override; void rowUpdateRow(not_null row) override; @@ -145,7 +150,6 @@ private: [[nodiscard]] bool allRowsAboveMoreImportantThanHand( not_null row, uint64 raiseHandRating) const; - Row *findRow(not_null 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 call, not_null 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, 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 real) { subscribeToChanges(real); @@ -413,7 +419,7 @@ void MembersController::setupListChangeViewers() { }, _lifetime); } -void MembersController::subscribeToChanges(not_null real) { +void Members::Controller::subscribeToChanges(not_null real) { _fullCount = real->fullCountValue(); real->participantsReloaded( @@ -449,30 +455,7 @@ void MembersController::subscribeToChanges(not_null 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(_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 &was, const Data::GroupCallParticipant &now) { auto reorderIfInvitedBefore = 0; @@ -561,7 +544,7 @@ void MembersController::updateRow( } } -bool MembersController::allRowsAboveAreSpeaking(not_null row) const { +bool Members::Controller::allRowsAboveAreSpeaking(not_null 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) const { return false; } -bool MembersController::allRowsAboveMoreImportantThanHand( +bool Members::Controller::allRowsAboveMoreImportantThanHand( not_null row, uint64 raiseHandRating) const { Expects(raiseHandRating > 0); @@ -598,7 +581,7 @@ bool MembersController::allRowsAboveMoreImportantThanHand( return false; } -bool MembersController::needToReorder(not_null row) const { +bool Members::Controller::needToReorder(not_null row) const { // All reorder cases: // - bring speaking up // - bring raised hand up @@ -634,7 +617,7 @@ bool MembersController::needToReorder(not_null row) const { return false; } -void MembersController::checkRowPosition(not_null row) { +void Members::Controller::checkRowPosition(not_null 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) { : makeComparator(projForOther)); } -void MembersController::updateRow( +void Members::Controller::updateRow( not_null 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) { +void Members::Controller::removeRow(not_null row) { _soundingRowBySsrc.remove(row->ssrc()); delegate()->peerListRemoveRow(row); } -void MembersController::updateRowLevel( +void Members::Controller::updateRowLevel( not_null row, float level) { if (_skipRowLevelUpdate) { @@ -731,12 +714,13 @@ void MembersController::updateRowLevel( row->updateLevel(level); } -Row *MembersController::findRow(not_null participantPeer) const { +Row *Members::Controller::findRow( + not_null participantPeer) const { return static_cast( 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 participant) const { return (participant->peer == _call->joinAs()) ? _call->screenSharingEndpoint() : participant->screenEndpoint(); } -const std::string &MembersController::computeCameraEndpoint( +const std::string &Members::Controller::computeCameraEndpoint( not_null 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 participantPeer) const { +bool Members::Controller::isMe(not_null participantPeer) const { return (_call->joinAs() == participantPeer); } -void MembersController::prepareRows(not_null real) { +void Members::Controller::prepareRows(not_null real) { auto foundMe = false; auto changed = false; const auto &participants = real->participants(); @@ -847,35 +831,35 @@ void MembersController::prepareRows(not_null 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 { return _toggleMuteRequests.events(); } -auto MembersController::changeVolumeRequests() const +auto Members::Controller::changeVolumeRequests() const -> rpl::producer { return _changeVolumeRequests.events(); } -bool MembersController::rowIsMe(not_null participantPeer) { +bool Members::Controller::rowIsMe(not_null participantPeer) { return isMe(participantPeer); } -bool MembersController::rowCanMuteMembers() { +bool Members::Controller::rowCanMuteMembers() { return _peer->canManageGroupCall(); } -void MembersController::rowUpdateRow(not_null row) { +void Members::Controller::rowUpdateRow(not_null row) { delegate()->peerListUpdateRow(row); } -void MembersController::rowScheduleRaisedHandStatusRemove( +void Members::Controller::rowScheduleRaisedHandStatusRemove( not_null 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 row, @@ -1067,7 +1065,7 @@ void MembersController::customRowPaint( selected); } -bool MembersController::customRowSelectionPoint( +bool Members::Controller::customRowSelectionPoint( not_null row, int x, int y) { @@ -1077,7 +1075,7 @@ bool MembersController::customRowSelectionPoint( && y < st::groupCallNarrowRowSkip + st::groupCallNarrowSize.height(); } -Fn MembersController::customRowRippleMaskGenerator() { +Fn Members::Controller::customRowRippleMaskGenerator() { return [] { return Ui::RippleAnimation::roundRectMask( st::groupCallNarrowSize, @@ -1085,12 +1083,12 @@ Fn MembersController::customRowRippleMaskGenerator() { }; } -auto MembersController::kickParticipantRequests() const +auto Members::Controller::kickParticipantRequests() const -> rpl::producer>{ return _kickParticipantRequests.events(); } -void MembersController::rowClicked(not_null row) { +void Members::Controller::rowClicked(not_null row) { delegate()->peerListShowRowMenu(row, [=](not_null menu) { if (!_menu || _menu.get() != menu) { return; @@ -1105,12 +1103,12 @@ void MembersController::rowClicked(not_null row) { }); } -void MembersController::rowActionClicked( +void Members::Controller::rowActionClicked( not_null row) { rowClicked(row); } -base::unique_qptr MembersController::rowContextMenu( +base::unique_qptr Members::Controller::rowContextMenu( QWidget *parent, not_null row) { auto result = createRowContextMenu(parent, row); @@ -1127,7 +1125,7 @@ base::unique_qptr MembersController::rowContextMenu( return result; } -base::unique_qptr MembersController::createRowContextMenu( +base::unique_qptr Members::Controller::createRowContextMenu( QWidget *parent, not_null row) { const auto participantPeer = row->peer(); @@ -1278,7 +1276,7 @@ base::unique_qptr MembersController::createRowContextMenu( return result; } -void MembersController::addMuteActionsToContextMenu( +void Members::Controller::addMuteActionsToContextMenu( not_null menu, not_null participantPeer, bool participantIsCallAdmin, @@ -1399,20 +1397,20 @@ void MembersController::addMuteActionsToContextMenu( } } -std::unique_ptr MembersController::createRowForMe() { +std::unique_ptr Members::Controller::createRowForMe() { auto result = std::make_unique(this, _call->joinAs()); updateRow(result.get(), nullptr); return result; } -std::unique_ptr MembersController::createRow( +std::unique_ptr Members::Controller::createRow( const Data::GroupCallParticipant &participant) { auto result = std::make_unique(this, participant.peer); updateRow(result.get(), &participant); return result; } -std::unique_ptr MembersController::createInvitedRow( +std::unique_ptr Members::Controller::createInvitedRow( not_null participantPeer) { if (findRow(participantPeer)) { return nullptr; @@ -1422,15 +1420,13 @@ std::unique_ptr MembersController::createInvitedRow( return result; } -} // namespace - Members::Members( not_null parent, not_null call) : RpWidget(parent) , _call(call) , _scroll(this) -, _listController(std::make_unique(call, parent)) +, _listController(std::make_unique(call, parent)) , _layout(_scroll->setOwnedWidget( object_ptr(_scroll.data()))) , _pinnedVideo(_layout->add(object_ptr(_layout.get()))) { @@ -1442,22 +1438,21 @@ Members::Members( _listController->setDelegate(static_cast(this)); } +Members::~Members() = default; + auto Members::toggleMuteRequests() const -> rpl::producer { - return static_cast( - _listController.get())->toggleMuteRequests(); + return _listController->toggleMuteRequests(); } auto Members::changeVolumeRequests() const -> rpl::producer { - return static_cast( - _listController.get())->changeVolumeRequests(); + return _listController->changeVolumeRequests(); } auto Members::kickParticipantRequests() const -> rpl::producer> { - return static_cast( - _listController.get())->kickParticipantRequests(); + return _listController->kickParticipantRequests(); } int Members::desiredHeight() const { @@ -1480,12 +1475,10 @@ int Members::desiredHeight() const { } rpl::producer Members::desiredHeightValue() const { - const auto controller = static_cast( - _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 call) { }, lifetime()); } +Row *Members::lookupRow(not_null 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 Members::fullCountValue() const { - return static_cast( - _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(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.h b/Telegram/SourceFiles/calls/group/calls_group_members.h index 5c43d8cf8b..7a830b68a1 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members.h @@ -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 parent, not_null call); + ~Members(); [[nodiscard]] int desiredHeight() const; [[nodiscard]] rpl::producer desiredHeightValue() const override; @@ -51,9 +53,12 @@ public: return _addMemberRequests.events(); } + [[nodiscard]] MembersRow *lookupRow(not_null 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 _call; rpl::variable _mode = PanelMode(); object_ptr _scroll; - std::unique_ptr _listController; + std::unique_ptr _listController; not_null _layout; const not_null _pinnedVideo; rpl::variable _addMemberButton = nullptr; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp index 965f86e20d..eca54186b3 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp @@ -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, }; } diff --git a/Telegram/SourceFiles/calls/group/calls_group_members_row.h b/Telegram/SourceFiles/calls/group/calls_group_members_row.h index 430d2800ec..ac2fbc423f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members_row.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members_row.h @@ -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 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 _delegate; State _state = State::Inactive; diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 101fe14e56..063f99359a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -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( + 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() { diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 54522397db..f82e4bc052 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -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 _menu = { nullptr }; object_ptr _joinAsToggle = { nullptr }; object_ptr _members = { nullptr }; - object_ptr _pinnedVideo = { nullptr }; + std::unique_ptr _pinnedVideo; rpl::lifetime _pinnedTrackLifetime; object_ptr _startsIn = { nullptr }; object_ptr _countdown = { nullptr }; diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 445d42bacf..63dcf796d7 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 445d42bacf80d8c7fa2a9d2ff790341cb656ff25 +Subproject commit 63dcf796d7721e02ee179c61aec7045b2699a3eb diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index 86ca2dd27e..0c867b0b6a 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit 86ca2dd27e52fa929423b61cd7861a0bc9483e28 +Subproject commit 0c867b0b6ae29e6ae5d5c2fda3824cdb595900eb