Show name and information on wide large video.
|
@ -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
|
||||
|
|
BIN
Telegram/Resources/icons/calls/voice_enlarge.png
Normal file
After Width: | Height: | Size: 692 B |
BIN
Telegram/Resources/icons/calls/voice_enlarge@2x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/calls/voice_enlarge@3x.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
Telegram/Resources/icons/calls/voice_minimize.png
Normal file
After Width: | Height: | Size: 546 B |
BIN
Telegram/Resources/icons/calls/voice_minimize@2x.png
Normal file
After Width: | Height: | Size: 1,001 B |
BIN
Telegram/Resources/icons/calls/voice_minimize@3x.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/calls/voice_pin.png
Normal file
After Width: | Height: | Size: 585 B |
BIN
Telegram/Resources/icons/calls/voice_pin@2x.png
Normal file
After Width: | Height: | Size: 961 B |
BIN
Telegram/Resources/icons/calls/voice_pin@3x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
252
Telegram/SourceFiles/calls/group/calls_group_large_video.cpp
Normal 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
|
107
Telegram/SourceFiles/calls/group/calls_group_large_video.h
Normal 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
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 };
|
||||
|
|
2
Telegram/ThirdParty/tgcalls
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 445d42bacf80d8c7fa2a9d2ff790341cb656ff25
|
||||
Subproject commit 63dcf796d7721e02ee179c61aec7045b2699a3eb
|
|
@ -1 +1 @@
|
|||
Subproject commit 86ca2dd27e52fa929423b61cd7861a0bc9483e28
|
||||
Subproject commit 0c867b0b6ae29e6ae5d5c2fda3824cdb595900eb
|