Show name and information on wide large video.
|
@ -277,6 +277,8 @@ PRIVATE
|
||||||
calls/group/calls_group_call.cpp
|
calls/group/calls_group_call.cpp
|
||||||
calls/group/calls_group_call.h
|
calls/group/calls_group_call.h
|
||||||
calls/group/calls_group_common.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.cpp
|
||||||
calls/group/calls_group_members.h
|
calls/group/calls_group_members.h
|
||||||
calls/group/calls_group_members_row.cpp
|
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;
|
fg: groupCallVideoTextFg;
|
||||||
icon: icon {{ "calls/voice_mute_mini", 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 {
|
struct GroupCall::SinkPointer {
|
||||||
std::shared_ptr<Webrtc::SinkInterface> data;
|
std::shared_ptr<Webrtc::SinkInterface> data;
|
||||||
};
|
};
|
||||||
|
@ -1847,18 +1839,18 @@ void GroupCall::ensureControllerCreated() {
|
||||||
_videoEndpointLarge.changes(
|
_videoEndpointLarge.changes(
|
||||||
) | rpl::start_with_next([=](const VideoEndpoint &endpoint) {
|
) | rpl::start_with_next([=](const VideoEndpoint &endpoint) {
|
||||||
_instance->setFullSizeVideoEndpointId(endpoint.endpoint);
|
_instance->setFullSizeVideoEndpointId(endpoint.endpoint);
|
||||||
_videoLargeTrack = nullptr;
|
_videoLargeTrack = LargeTrack();
|
||||||
_videoLargeTrackWrap = nullptr;
|
_videoLargeTrackWrap = nullptr;
|
||||||
if (endpoint.empty()) {
|
if (!endpoint) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!_videoLargeTrackWrap) {
|
_videoLargeTrackWrap = std::make_unique<Webrtc::VideoTrack>(
|
||||||
_videoLargeTrackWrap = std::make_unique<LargeTrack>();
|
Webrtc::VideoState::Active);
|
||||||
_videoLargeTrack = &_videoLargeTrackWrap->track;
|
_videoLargeTrack = LargeTrack{
|
||||||
}
|
_videoLargeTrackWrap.get(),
|
||||||
_videoLargeTrackWrap->sink = Webrtc::CreateProxySink(
|
endpoint.peer
|
||||||
_videoLargeTrackWrap->track.sink());
|
};
|
||||||
addVideoOutput(endpoint.endpoint, { _videoLargeTrackWrap->sink });
|
addVideoOutput(endpoint.endpoint, { _videoLargeTrackWrap->sink() });
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
|
|
||||||
updateInstanceMuteState();
|
updateInstanceMuteState();
|
||||||
|
|
|
@ -297,11 +297,25 @@ public:
|
||||||
-> rpl::producer<VideoEndpoint> {
|
-> rpl::producer<VideoEndpoint> {
|
||||||
return _videoEndpointLarge.value();
|
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();
|
return _videoLargeTrack.current();
|
||||||
}
|
}
|
||||||
[[nodiscard]] auto videoLargeTrackValue() const
|
[[nodiscard]] auto videoLargeTrackValue() const
|
||||||
-> rpl::producer<Webrtc::VideoTrack*> {
|
-> rpl::producer<LargeTrack> {
|
||||||
return _videoLargeTrack.value();
|
return _videoLargeTrack.value();
|
||||||
}
|
}
|
||||||
[[nodiscard]] rpl::producer<Group::RejoinEvent> rejoinEvents() const {
|
[[nodiscard]] rpl::producer<Group::RejoinEvent> rejoinEvents() const {
|
||||||
|
@ -355,7 +369,6 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using GlobalShortcutValue = base::GlobalShortcutValue;
|
using GlobalShortcutValue = base::GlobalShortcutValue;
|
||||||
struct LargeTrack;
|
|
||||||
struct SinkPointer;
|
struct SinkPointer;
|
||||||
|
|
||||||
struct LoadingPart {
|
struct LoadingPart {
|
||||||
|
@ -524,8 +537,8 @@ private:
|
||||||
base::flat_map<std::string, EndpointType> _activeVideoEndpoints;
|
base::flat_map<std::string, EndpointType> _activeVideoEndpoints;
|
||||||
rpl::variable<VideoEndpoint> _videoEndpointLarge;
|
rpl::variable<VideoEndpoint> _videoEndpointLarge;
|
||||||
rpl::variable<bool> _videoEndpointPinned;
|
rpl::variable<bool> _videoEndpointPinned;
|
||||||
std::unique_ptr<LargeTrack> _videoLargeTrackWrap;
|
std::unique_ptr<Webrtc::VideoTrack> _videoLargeTrackWrap;
|
||||||
rpl::variable<Webrtc::VideoTrack*> _videoLargeTrack;
|
rpl::variable<LargeTrack> _videoLargeTrack;
|
||||||
base::flat_map<uint32, Data::LastSpokeTimes> _lastSpoke;
|
base::flat_map<uint32, Data::LastSpokeTimes> _lastSpoke;
|
||||||
rpl::event_stream<Group::RejoinEvent> _rejoinEvents;
|
rpl::event_stream<Group::RejoinEvent> _rejoinEvents;
|
||||||
rpl::event_stream<> _allowedToSpeakNotifications;
|
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_group_menu.h"
|
||||||
#include "calls/group/calls_volume_item.h"
|
#include "calls/group/calls_volume_item.h"
|
||||||
#include "calls/group/calls_group_members_row.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_channel.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
@ -43,15 +44,17 @@ constexpr auto kShadowMaxAlpha = 74;
|
||||||
|
|
||||||
using Row = MembersRow;
|
using Row = MembersRow;
|
||||||
|
|
||||||
class MembersController final
|
} // namespace
|
||||||
|
|
||||||
|
class Members::Controller final
|
||||||
: public PeerListController
|
: public PeerListController
|
||||||
, public MembersRowDelegate
|
, public MembersRowDelegate
|
||||||
, public base::has_weak_ptr {
|
, public base::has_weak_ptr {
|
||||||
public:
|
public:
|
||||||
MembersController(
|
Controller(
|
||||||
not_null<GroupCall*> call,
|
not_null<GroupCall*> call,
|
||||||
not_null<QWidget*> menuParent);
|
not_null<QWidget*> menuParent);
|
||||||
~MembersController();
|
~Controller();
|
||||||
|
|
||||||
using MuteRequest = Group::MuteRequest;
|
using MuteRequest = Group::MuteRequest;
|
||||||
using VolumeRequest = Group::VolumeRequest;
|
using VolumeRequest = Group::VolumeRequest;
|
||||||
|
@ -73,6 +76,8 @@ public:
|
||||||
[[nodiscard]] auto kickParticipantRequests() const
|
[[nodiscard]] auto kickParticipantRequests() const
|
||||||
-> rpl::producer<not_null<PeerData*>>;
|
-> rpl::producer<not_null<PeerData*>>;
|
||||||
|
|
||||||
|
Row *findRow(not_null<PeerData*> participantPeer) const;
|
||||||
|
|
||||||
bool rowIsMe(not_null<PeerData*> participantPeer) override;
|
bool rowIsMe(not_null<PeerData*> participantPeer) override;
|
||||||
bool rowCanMuteMembers() override;
|
bool rowCanMuteMembers() override;
|
||||||
void rowUpdateRow(not_null<Row*> row) override;
|
void rowUpdateRow(not_null<Row*> row) override;
|
||||||
|
@ -145,7 +150,6 @@ private:
|
||||||
[[nodiscard]] bool allRowsAboveMoreImportantThanHand(
|
[[nodiscard]] bool allRowsAboveMoreImportantThanHand(
|
||||||
not_null<Row*> row,
|
not_null<Row*> row,
|
||||||
uint64 raiseHandRating) const;
|
uint64 raiseHandRating) const;
|
||||||
Row *findRow(not_null<PeerData*> participantPeer) const;
|
|
||||||
const Data::GroupCallParticipant *findParticipant(
|
const Data::GroupCallParticipant *findParticipant(
|
||||||
const std::string &endpoint) const;
|
const std::string &endpoint) const;
|
||||||
const std::string &computeScreenEndpoint(
|
const std::string &computeScreenEndpoint(
|
||||||
|
@ -189,6 +193,7 @@ private:
|
||||||
Ui::CrossLineAnimation _inactiveNarrowCrossLine;
|
Ui::CrossLineAnimation _inactiveNarrowCrossLine;
|
||||||
Ui::CrossLineAnimation _coloredNarrowCrossLine;
|
Ui::CrossLineAnimation _coloredNarrowCrossLine;
|
||||||
Ui::CrossLineAnimation _videoNarrowCrossLine;
|
Ui::CrossLineAnimation _videoNarrowCrossLine;
|
||||||
|
Ui::CrossLineAnimation _videoLargeCrossLine;
|
||||||
Ui::RoundRect _narrowRoundRectSelected;
|
Ui::RoundRect _narrowRoundRectSelected;
|
||||||
Ui::RoundRect _narrowRoundRect;
|
Ui::RoundRect _narrowRoundRect;
|
||||||
QImage _narrowShadow;
|
QImage _narrowShadow;
|
||||||
|
@ -197,7 +202,7 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
MembersController::MembersController(
|
Members::Controller::Controller(
|
||||||
not_null<GroupCall*> call,
|
not_null<GroupCall*> call,
|
||||||
not_null<QWidget*> menuParent)
|
not_null<QWidget*> menuParent)
|
||||||
: _call(call)
|
: _call(call)
|
||||||
|
@ -209,6 +214,7 @@ MembersController::MembersController(
|
||||||
, _inactiveNarrowCrossLine(st::groupCallNarrowInactiveCrossLine)
|
, _inactiveNarrowCrossLine(st::groupCallNarrowInactiveCrossLine)
|
||||||
, _coloredNarrowCrossLine(st::groupCallNarrowColoredCrossLine)
|
, _coloredNarrowCrossLine(st::groupCallNarrowColoredCrossLine)
|
||||||
, _videoNarrowCrossLine(st::groupCallVideoCrossLine)
|
, _videoNarrowCrossLine(st::groupCallVideoCrossLine)
|
||||||
|
, _videoLargeCrossLine(st::groupCallLargeVideoCrossLine)
|
||||||
, _narrowRoundRectSelected(
|
, _narrowRoundRectSelected(
|
||||||
ImageRoundRadius::Large,
|
ImageRoundRadius::Large,
|
||||||
st::groupCallMembersBgOver)
|
st::groupCallMembersBgOver)
|
||||||
|
@ -267,11 +273,11 @@ MembersController::MembersController(
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
MembersController::~MembersController() {
|
Members::Controller::~Controller() {
|
||||||
base::take(_menu);
|
base::take(_menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::setRowVideoEndpoint(
|
void Members::Controller::setRowVideoEndpoint(
|
||||||
not_null<Row*> row,
|
not_null<Row*> row,
|
||||||
const std::string &endpoint) {
|
const std::string &endpoint) {
|
||||||
const auto was = row->videoTrackEndpoint();
|
const auto was = row->videoTrackEndpoint();
|
||||||
|
@ -290,7 +296,7 @@ void MembersController::setRowVideoEndpoint(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::setupListChangeViewers() {
|
void Members::Controller::setupListChangeViewers() {
|
||||||
_call->real(
|
_call->real(
|
||||||
) | rpl::start_with_next([=](not_null<Data::GroupCall*> real) {
|
) | rpl::start_with_next([=](not_null<Data::GroupCall*> real) {
|
||||||
subscribeToChanges(real);
|
subscribeToChanges(real);
|
||||||
|
@ -413,7 +419,7 @@ void MembersController::setupListChangeViewers() {
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
void Members::Controller::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||||
_fullCount = real->fullCountValue();
|
_fullCount = real->fullCountValue();
|
||||||
|
|
||||||
real->participantsReloaded(
|
real->participantsReloaded(
|
||||||
|
@ -449,30 +455,7 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::generateNarrowShadow() {
|
void Members::Controller::appendInvitedUsers() {
|
||||||
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() {
|
|
||||||
if (const auto id = _call->id()) {
|
if (const auto id = _call->id()) {
|
||||||
for (const auto user : _peer->owner().invitedToCallUsers(id)) {
|
for (const auto user : _peer->owner().invitedToCallUsers(id)) {
|
||||||
if (auto row = createInvitedRow(user)) {
|
if (auto row = createInvitedRow(user)) {
|
||||||
|
@ -494,7 +477,7 @@ void MembersController::appendInvitedUsers() {
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::updateRow(
|
void Members::Controller::updateRow(
|
||||||
const std::optional<Data::GroupCallParticipant> &was,
|
const std::optional<Data::GroupCallParticipant> &was,
|
||||||
const Data::GroupCallParticipant &now) {
|
const Data::GroupCallParticipant &now) {
|
||||||
auto reorderIfInvitedBefore = 0;
|
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();
|
const auto count = delegate()->peerListFullRowsCount();
|
||||||
for (auto i = 0; i != count; ++i) {
|
for (auto i = 0; i != count; ++i) {
|
||||||
const auto above = delegate()->peerListRowAt(i);
|
const auto above = delegate()->peerListRowAt(i);
|
||||||
|
@ -575,7 +558,7 @@ bool MembersController::allRowsAboveAreSpeaking(not_null<Row*> row) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MembersController::allRowsAboveMoreImportantThanHand(
|
bool Members::Controller::allRowsAboveMoreImportantThanHand(
|
||||||
not_null<Row*> row,
|
not_null<Row*> row,
|
||||||
uint64 raiseHandRating) const {
|
uint64 raiseHandRating) const {
|
||||||
Expects(raiseHandRating > 0);
|
Expects(raiseHandRating > 0);
|
||||||
|
@ -598,7 +581,7 @@ bool MembersController::allRowsAboveMoreImportantThanHand(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MembersController::needToReorder(not_null<Row*> row) const {
|
bool Members::Controller::needToReorder(not_null<Row*> row) const {
|
||||||
// All reorder cases:
|
// All reorder cases:
|
||||||
// - bring speaking up
|
// - bring speaking up
|
||||||
// - bring raised hand up
|
// - bring raised hand up
|
||||||
|
@ -634,7 +617,7 @@ bool MembersController::needToReorder(not_null<Row*> row) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::checkRowPosition(not_null<Row*> row) {
|
void Members::Controller::checkRowPosition(not_null<Row*> row) {
|
||||||
if (_menu) {
|
if (_menu) {
|
||||||
// Don't reorder rows while we show the popup menu.
|
// Don't reorder rows while we show the popup menu.
|
||||||
_menuCheckRowsAfterHidden.emplace(row->peer());
|
_menuCheckRowsAfterHidden.emplace(row->peer());
|
||||||
|
@ -680,7 +663,7 @@ void MembersController::checkRowPosition(not_null<Row*> row) {
|
||||||
: makeComparator(projForOther));
|
: makeComparator(projForOther));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::updateRow(
|
void Members::Controller::updateRow(
|
||||||
not_null<Row*> row,
|
not_null<Row*> row,
|
||||||
const Data::GroupCallParticipant *participant) {
|
const Data::GroupCallParticipant *participant) {
|
||||||
const auto wasSounding = row->sounding();
|
const auto wasSounding = row->sounding();
|
||||||
|
@ -717,12 +700,12 @@ void MembersController::updateRow(
|
||||||
delegate()->peerListUpdateRow(row);
|
delegate()->peerListUpdateRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::removeRow(not_null<Row*> row) {
|
void Members::Controller::removeRow(not_null<Row*> row) {
|
||||||
_soundingRowBySsrc.remove(row->ssrc());
|
_soundingRowBySsrc.remove(row->ssrc());
|
||||||
delegate()->peerListRemoveRow(row);
|
delegate()->peerListRemoveRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::updateRowLevel(
|
void Members::Controller::updateRowLevel(
|
||||||
not_null<Row*> row,
|
not_null<Row*> row,
|
||||||
float level) {
|
float level) {
|
||||||
if (_skipRowLevelUpdate) {
|
if (_skipRowLevelUpdate) {
|
||||||
|
@ -731,12 +714,13 @@ void MembersController::updateRowLevel(
|
||||||
row->updateLevel(level);
|
row->updateLevel(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
Row *MembersController::findRow(not_null<PeerData*> participantPeer) const {
|
Row *Members::Controller::findRow(
|
||||||
|
not_null<PeerData*> participantPeer) const {
|
||||||
return static_cast<Row*>(
|
return static_cast<Row*>(
|
||||||
delegate()->peerListFindRow(participantPeer->id.value));
|
delegate()->peerListFindRow(participantPeer->id.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
const Data::GroupCallParticipant *MembersController::findParticipant(
|
const Data::GroupCallParticipant *Members::Controller::findParticipant(
|
||||||
const std::string &endpoint) const {
|
const std::string &endpoint) const {
|
||||||
if (endpoint.empty()) {
|
if (endpoint.empty()) {
|
||||||
return nullptr;
|
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 {
|
not_null<const Data::GroupCallParticipant*> participant) const {
|
||||||
return (participant->peer == _call->joinAs())
|
return (participant->peer == _call->joinAs())
|
||||||
? _call->screenSharingEndpoint()
|
? _call->screenSharingEndpoint()
|
||||||
: participant->screenEndpoint();
|
: participant->screenEndpoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string &MembersController::computeCameraEndpoint(
|
const std::string &Members::Controller::computeCameraEndpoint(
|
||||||
not_null<const Data::GroupCallParticipant*> participant) const {
|
not_null<const Data::GroupCallParticipant*> participant) const {
|
||||||
return (participant->peer == _call->joinAs())
|
return (participant->peer == _call->joinAs())
|
||||||
? _call->cameraSharingEndpoint()
|
? _call->cameraSharingEndpoint()
|
||||||
: participant->cameraEndpoint();
|
: participant->cameraEndpoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
Main::Session &MembersController::session() const {
|
Main::Session &Members::Controller::session() const {
|
||||||
return _call->peer()->session();
|
return _call->peer()->session();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::prepare() {
|
void Members::Controller::prepare() {
|
||||||
delegate()->peerListSetSearchMode(PeerListSearchMode::Disabled);
|
delegate()->peerListSetSearchMode(PeerListSearchMode::Disabled);
|
||||||
//delegate()->peerListSetTitle(std::move(title));
|
//delegate()->peerListSetTitle(std::move(title));
|
||||||
setDescriptionText(tr::lng_contacts_loading(tr::now));
|
setDescriptionText(tr::lng_contacts_loading(tr::now));
|
||||||
|
@ -793,11 +777,11 @@ void MembersController::prepare() {
|
||||||
_prepared = true;
|
_prepared = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MembersController::isMe(not_null<PeerData*> participantPeer) const {
|
bool Members::Controller::isMe(not_null<PeerData*> participantPeer) const {
|
||||||
return (_call->joinAs() == participantPeer);
|
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 foundMe = false;
|
||||||
auto changed = false;
|
auto changed = false;
|
||||||
const auto &participants = real->participants();
|
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()) {
|
if (const auto real = _call->lookupReal()) {
|
||||||
real->requestParticipants();
|
real->requestParticipants();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MembersController::toggleMuteRequests() const
|
auto Members::Controller::toggleMuteRequests() const
|
||||||
-> rpl::producer<MuteRequest> {
|
-> rpl::producer<MuteRequest> {
|
||||||
return _toggleMuteRequests.events();
|
return _toggleMuteRequests.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MembersController::changeVolumeRequests() const
|
auto Members::Controller::changeVolumeRequests() const
|
||||||
-> rpl::producer<VolumeRequest> {
|
-> rpl::producer<VolumeRequest> {
|
||||||
return _changeVolumeRequests.events();
|
return _changeVolumeRequests.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MembersController::rowIsMe(not_null<PeerData*> participantPeer) {
|
bool Members::Controller::rowIsMe(not_null<PeerData*> participantPeer) {
|
||||||
return isMe(participantPeer);
|
return isMe(participantPeer);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MembersController::rowCanMuteMembers() {
|
bool Members::Controller::rowCanMuteMembers() {
|
||||||
return _peer->canManageGroupCall();
|
return _peer->canManageGroupCall();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::rowUpdateRow(not_null<Row*> row) {
|
void Members::Controller::rowUpdateRow(not_null<Row*> row) {
|
||||||
delegate()->peerListUpdateRow(row);
|
delegate()->peerListUpdateRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::rowScheduleRaisedHandStatusRemove(
|
void Members::Controller::rowScheduleRaisedHandStatusRemove(
|
||||||
not_null<Row*> row) {
|
not_null<Row*> row) {
|
||||||
const auto id = row->id();
|
const auto id = row->id();
|
||||||
const auto when = crl::now() + kKeepRaisedHandStatusDuration;
|
const auto when = crl::now() + kKeepRaisedHandStatusDuration;
|
||||||
|
@ -888,7 +872,7 @@ void MembersController::rowScheduleRaisedHandStatusRemove(
|
||||||
scheduleRaisedHandStatusRemove();
|
scheduleRaisedHandStatusRemove();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::scheduleRaisedHandStatusRemove() {
|
void Members::Controller::scheduleRaisedHandStatusRemove() {
|
||||||
auto waiting = crl::time(0);
|
auto waiting = crl::time(0);
|
||||||
const auto now = crl::now();
|
const auto now = crl::now();
|
||||||
for (auto i = begin(_raisedHandStatusRemoveAt)
|
for (auto i = begin(_raisedHandStatusRemoveAt)
|
||||||
|
@ -913,13 +897,16 @@ void MembersController::scheduleRaisedHandStatusRemove() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::rowPaintIcon(
|
void Members::Controller::rowPaintIcon(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
QRect rect,
|
QRect rect,
|
||||||
const IconState &state) {
|
const IconState &state) {
|
||||||
const auto narrowUserpic = (state.narrowStyle == NarrowStyle::Userpic);
|
const auto narrowUserpic = (state.style == MembersRowStyle::Userpic);
|
||||||
const auto narrowVideo = (state.narrowStyle == NarrowStyle::Video);
|
const auto narrowVideo = (state.style == MembersRowStyle::Video);
|
||||||
const auto &greenIcon = narrowVideo
|
const auto largeVideo = (state.style == MembersRowStyle::LargeVideo);
|
||||||
|
const auto &greenIcon = largeVideo
|
||||||
|
? st::groupCallLargeVideoCrossLine.icon
|
||||||
|
: narrowVideo
|
||||||
? st::groupCallVideoCrossLine.icon
|
? st::groupCallVideoCrossLine.icon
|
||||||
: narrowUserpic
|
: narrowUserpic
|
||||||
? st::groupCallNarrowColoredCrossLine.icon
|
? st::groupCallNarrowColoredCrossLine.icon
|
||||||
|
@ -933,7 +920,9 @@ void MembersController::rowPaintIcon(
|
||||||
} else if (state.speaking == 0.) {
|
} else if (state.speaking == 0.) {
|
||||||
if (state.active == 1.) {
|
if (state.active == 1.) {
|
||||||
// Just gray icon, no cross, no coloring.
|
// Just gray icon, no cross, no coloring.
|
||||||
const auto &grayIcon = narrowVideo
|
const auto &grayIcon = largeVideo
|
||||||
|
? st::groupCallLargeVideoCrossLine.icon
|
||||||
|
: narrowVideo
|
||||||
? st::groupCallVideoCrossLine.icon
|
? st::groupCallVideoCrossLine.icon
|
||||||
: narrowUserpic
|
: narrowUserpic
|
||||||
? st::groupCallNarrowInactiveCrossLine.icon
|
? st::groupCallNarrowInactiveCrossLine.icon
|
||||||
|
@ -948,12 +937,14 @@ void MembersController::rowPaintIcon(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Red crossed icon, colorized once, cached as last frame.
|
// Red crossed icon, colorized once, cached as last frame.
|
||||||
auto &line = narrowVideo
|
auto &line = largeVideo
|
||||||
|
? _videoLargeCrossLine
|
||||||
|
: narrowVideo
|
||||||
? _videoNarrowCrossLine
|
? _videoNarrowCrossLine
|
||||||
: narrowUserpic
|
: narrowUserpic
|
||||||
? _coloredNarrowCrossLine
|
? _coloredNarrowCrossLine
|
||||||
: _coloredCrossLine;
|
: _coloredCrossLine;
|
||||||
const auto color = narrowVideo
|
const auto color = (largeVideo || narrowVideo)
|
||||||
? std::nullopt
|
? std::nullopt
|
||||||
: std::make_optional(st::groupCallMemberMutedIcon->c);
|
: std::make_optional(st::groupCallMemberMutedIcon->c);
|
||||||
line.paint(
|
line.paint(
|
||||||
|
@ -965,7 +956,9 @@ void MembersController::rowPaintIcon(
|
||||||
return;
|
return;
|
||||||
} else if (state.muted == 0.) {
|
} else if (state.muted == 0.) {
|
||||||
// Gray crossed icon, no coloring, cached as last frame.
|
// Gray crossed icon, no coloring, cached as last frame.
|
||||||
auto &line = narrowVideo
|
auto &line = largeVideo
|
||||||
|
? _videoLargeCrossLine
|
||||||
|
: narrowVideo
|
||||||
? _videoNarrowCrossLine
|
? _videoNarrowCrossLine
|
||||||
: narrowUserpic
|
: narrowUserpic
|
||||||
? _inactiveNarrowCrossLine
|
? _inactiveNarrowCrossLine
|
||||||
|
@ -985,14 +978,16 @@ void MembersController::rowPaintIcon(
|
||||||
activeInactiveColor,
|
activeInactiveColor,
|
||||||
st::groupCallMemberMutedIcon,
|
st::groupCallMemberMutedIcon,
|
||||||
state.muted);
|
state.muted);
|
||||||
const auto color = narrowVideo
|
const auto color = (largeVideo || narrowVideo)
|
||||||
? std::nullopt
|
? std::nullopt
|
||||||
: std::make_optional(iconColor);
|
: std::make_optional(iconColor);
|
||||||
|
|
||||||
// Don't use caching of the last frame,
|
// Don't use caching of the last frame,
|
||||||
// because 'muted' may animate color.
|
// because 'muted' may animate color.
|
||||||
const auto crossProgress = std::min(1. - state.active, 0.9999);
|
const auto crossProgress = std::min(1. - state.active, 0.9999);
|
||||||
auto &line = narrowVideo
|
auto &line = largeVideo
|
||||||
|
? _videoLargeCrossLine
|
||||||
|
: narrowVideo
|
||||||
? _videoNarrowCrossLine
|
? _videoNarrowCrossLine
|
||||||
: narrowUserpic
|
: narrowUserpic
|
||||||
? _inactiveNarrowCrossLine
|
? _inactiveNarrowCrossLine
|
||||||
|
@ -1000,7 +995,7 @@ void MembersController::rowPaintIcon(
|
||||||
line.paint(p, left, top, crossProgress, color);
|
line.paint(p, left, top, crossProgress, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::rowPaintNarrowBackground(
|
void Members::Controller::rowPaintNarrowBackground(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
int x,
|
int x,
|
||||||
int y,
|
int y,
|
||||||
|
@ -1010,7 +1005,7 @@ void MembersController::rowPaintNarrowBackground(
|
||||||
{ QPoint(x, y), st::groupCallNarrowSize });
|
{ QPoint(x, y), st::groupCallNarrowSize });
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::rowPaintNarrowBorder(
|
void Members::Controller::rowPaintNarrowBorder(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
int x,
|
int x,
|
||||||
int y,
|
int y,
|
||||||
|
@ -1029,14 +1024,17 @@ void MembersController::rowPaintNarrowBorder(
|
||||||
st::roundRadiusLarge);
|
st::roundRadiusLarge);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::rowPaintNarrowShadow(
|
void Members::Controller::rowPaintNarrowShadow(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
int x,
|
int x,
|
||||||
int y,
|
int y,
|
||||||
int sizew,
|
int sizew,
|
||||||
int sizeh) {
|
int sizeh) {
|
||||||
if (_narrowShadow.isNull()) {
|
if (_narrowShadow.isNull()) {
|
||||||
generateNarrowShadow();
|
_narrowShadow = GenerateShadow(
|
||||||
|
st::groupCallNarrowShadowHeight,
|
||||||
|
0,
|
||||||
|
kShadowMaxAlpha);
|
||||||
}
|
}
|
||||||
const auto height = st::groupCallNarrowShadowHeight;
|
const auto height = st::groupCallNarrowShadowHeight;
|
||||||
p.drawImage(
|
p.drawImage(
|
||||||
|
@ -1044,11 +1042,11 @@ void MembersController::rowPaintNarrowShadow(
|
||||||
_narrowShadow);
|
_narrowShadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MembersController::customRowHeight() {
|
int Members::Controller::customRowHeight() {
|
||||||
return st::groupCallNarrowSize.height() + st::groupCallNarrowRowSkip * 2;
|
return st::groupCallNarrowSize.height() + st::groupCallNarrowRowSkip * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::customRowPaint(
|
void Members::Controller::customRowPaint(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
crl::time now,
|
crl::time now,
|
||||||
not_null<PeerListRow*> row,
|
not_null<PeerListRow*> row,
|
||||||
|
@ -1067,7 +1065,7 @@ void MembersController::customRowPaint(
|
||||||
selected);
|
selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MembersController::customRowSelectionPoint(
|
bool Members::Controller::customRowSelectionPoint(
|
||||||
not_null<PeerListRow*> row,
|
not_null<PeerListRow*> row,
|
||||||
int x,
|
int x,
|
||||||
int y) {
|
int y) {
|
||||||
|
@ -1077,7 +1075,7 @@ bool MembersController::customRowSelectionPoint(
|
||||||
&& y < st::groupCallNarrowRowSkip + st::groupCallNarrowSize.height();
|
&& y < st::groupCallNarrowRowSkip + st::groupCallNarrowSize.height();
|
||||||
}
|
}
|
||||||
|
|
||||||
Fn<QImage()> MembersController::customRowRippleMaskGenerator() {
|
Fn<QImage()> Members::Controller::customRowRippleMaskGenerator() {
|
||||||
return [] {
|
return [] {
|
||||||
return Ui::RippleAnimation::roundRectMask(
|
return Ui::RippleAnimation::roundRectMask(
|
||||||
st::groupCallNarrowSize,
|
st::groupCallNarrowSize,
|
||||||
|
@ -1085,12 +1083,12 @@ Fn<QImage()> MembersController::customRowRippleMaskGenerator() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MembersController::kickParticipantRequests() const
|
auto Members::Controller::kickParticipantRequests() const
|
||||||
-> rpl::producer<not_null<PeerData*>>{
|
-> rpl::producer<not_null<PeerData*>>{
|
||||||
return _kickParticipantRequests.events();
|
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) {
|
delegate()->peerListShowRowMenu(row, [=](not_null<Ui::PopupMenu*> menu) {
|
||||||
if (!_menu || _menu.get() != menu) {
|
if (!_menu || _menu.get() != menu) {
|
||||||
return;
|
return;
|
||||||
|
@ -1105,12 +1103,12 @@ void MembersController::rowClicked(not_null<PeerListRow*> row) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::rowActionClicked(
|
void Members::Controller::rowActionClicked(
|
||||||
not_null<PeerListRow*> row) {
|
not_null<PeerListRow*> row) {
|
||||||
rowClicked(row);
|
rowClicked(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
base::unique_qptr<Ui::PopupMenu> MembersController::rowContextMenu(
|
base::unique_qptr<Ui::PopupMenu> Members::Controller::rowContextMenu(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
not_null<PeerListRow*> row) {
|
not_null<PeerListRow*> row) {
|
||||||
auto result = createRowContextMenu(parent, row);
|
auto result = createRowContextMenu(parent, row);
|
||||||
|
@ -1127,7 +1125,7 @@ base::unique_qptr<Ui::PopupMenu> MembersController::rowContextMenu(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
|
base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
not_null<PeerListRow*> row) {
|
not_null<PeerListRow*> row) {
|
||||||
const auto participantPeer = row->peer();
|
const auto participantPeer = row->peer();
|
||||||
|
@ -1278,7 +1276,7 @@ base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MembersController::addMuteActionsToContextMenu(
|
void Members::Controller::addMuteActionsToContextMenu(
|
||||||
not_null<Ui::PopupMenu*> menu,
|
not_null<Ui::PopupMenu*> menu,
|
||||||
not_null<PeerData*> participantPeer,
|
not_null<PeerData*> participantPeer,
|
||||||
bool participantIsCallAdmin,
|
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());
|
auto result = std::make_unique<Row>(this, _call->joinAs());
|
||||||
updateRow(result.get(), nullptr);
|
updateRow(result.get(), nullptr);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Row> MembersController::createRow(
|
std::unique_ptr<Row> Members::Controller::createRow(
|
||||||
const Data::GroupCallParticipant &participant) {
|
const Data::GroupCallParticipant &participant) {
|
||||||
auto result = std::make_unique<Row>(this, participant.peer);
|
auto result = std::make_unique<Row>(this, participant.peer);
|
||||||
updateRow(result.get(), &participant);
|
updateRow(result.get(), &participant);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Row> MembersController::createInvitedRow(
|
std::unique_ptr<Row> Members::Controller::createInvitedRow(
|
||||||
not_null<PeerData*> participantPeer) {
|
not_null<PeerData*> participantPeer) {
|
||||||
if (findRow(participantPeer)) {
|
if (findRow(participantPeer)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -1422,15 +1420,13 @@ std::unique_ptr<Row> MembersController::createInvitedRow(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
Members::Members(
|
Members::Members(
|
||||||
not_null<QWidget*> parent,
|
not_null<QWidget*> parent,
|
||||||
not_null<GroupCall*> call)
|
not_null<GroupCall*> call)
|
||||||
: RpWidget(parent)
|
: RpWidget(parent)
|
||||||
, _call(call)
|
, _call(call)
|
||||||
, _scroll(this)
|
, _scroll(this)
|
||||||
, _listController(std::make_unique<MembersController>(call, parent))
|
, _listController(std::make_unique<Controller>(call, parent))
|
||||||
, _layout(_scroll->setOwnedWidget(
|
, _layout(_scroll->setOwnedWidget(
|
||||||
object_ptr<Ui::VerticalLayout>(_scroll.data())))
|
object_ptr<Ui::VerticalLayout>(_scroll.data())))
|
||||||
, _pinnedVideo(_layout->add(object_ptr<Ui::RpWidget>(_layout.get()))) {
|
, _pinnedVideo(_layout->add(object_ptr<Ui::RpWidget>(_layout.get()))) {
|
||||||
|
@ -1442,22 +1438,21 @@ Members::Members(
|
||||||
_listController->setDelegate(static_cast<PeerListDelegate*>(this));
|
_listController->setDelegate(static_cast<PeerListDelegate*>(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Members::~Members() = default;
|
||||||
|
|
||||||
auto Members::toggleMuteRequests() const
|
auto Members::toggleMuteRequests() const
|
||||||
-> rpl::producer<Group::MuteRequest> {
|
-> rpl::producer<Group::MuteRequest> {
|
||||||
return static_cast<MembersController*>(
|
return _listController->toggleMuteRequests();
|
||||||
_listController.get())->toggleMuteRequests();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Members::changeVolumeRequests() const
|
auto Members::changeVolumeRequests() const
|
||||||
-> rpl::producer<Group::VolumeRequest> {
|
-> rpl::producer<Group::VolumeRequest> {
|
||||||
return static_cast<MembersController*>(
|
return _listController->changeVolumeRequests();
|
||||||
_listController.get())->changeVolumeRequests();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Members::kickParticipantRequests() const
|
auto Members::kickParticipantRequests() const
|
||||||
-> rpl::producer<not_null<PeerData*>> {
|
-> rpl::producer<not_null<PeerData*>> {
|
||||||
return static_cast<MembersController*>(
|
return _listController->kickParticipantRequests();
|
||||||
_listController.get())->kickParticipantRequests();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int Members::desiredHeight() const {
|
int Members::desiredHeight() const {
|
||||||
|
@ -1480,12 +1475,10 @@ int Members::desiredHeight() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<int> Members::desiredHeightValue() const {
|
rpl::producer<int> Members::desiredHeightValue() const {
|
||||||
const auto controller = static_cast<MembersController*>(
|
|
||||||
_listController.get());
|
|
||||||
return rpl::combine(
|
return rpl::combine(
|
||||||
heightValue(),
|
heightValue(),
|
||||||
_addMemberButton.value(),
|
_addMemberButton.value(),
|
||||||
controller->fullCountValue()
|
_listController->fullCountValue()
|
||||||
) | rpl::map([=] {
|
) | rpl::map([=] {
|
||||||
return desiredHeight();
|
return desiredHeight();
|
||||||
});
|
});
|
||||||
|
@ -1572,6 +1565,10 @@ void Members::setupAddMember(not_null<GroupCall*> call) {
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row *Members::lookupRow(not_null<PeerData*> peer) const {
|
||||||
|
return _listController->findRow(peer);
|
||||||
|
}
|
||||||
|
|
||||||
void Members::setMode(PanelMode mode) {
|
void Members::setMode(PanelMode mode) {
|
||||||
if (_mode.current() == mode) {
|
if (_mode.current() == mode) {
|
||||||
return;
|
return;
|
||||||
|
@ -1583,8 +1580,7 @@ void Members::setMode(PanelMode mode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<int> Members::fullCountValue() const {
|
rpl::producer<int> Members::fullCountValue() const {
|
||||||
return static_cast<MembersController*>(
|
return _listController->fullCountValue();
|
||||||
_listController.get())->fullCountValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Members::setupList() {
|
void Members::setupList() {
|
||||||
|
@ -1624,8 +1620,8 @@ void Members::setupPinnedVideo() {
|
||||||
rpl::combine(
|
rpl::combine(
|
||||||
_mode.value(),
|
_mode.value(),
|
||||||
_call->videoLargeTrackValue()
|
_call->videoLargeTrackValue()
|
||||||
) | rpl::map([](PanelMode mode, Webrtc::VideoTrack *track) {
|
) | rpl::map([](PanelMode mode, GroupCall::LargeTrack track) {
|
||||||
return (mode == PanelMode::Default) ? track : nullptr;
|
return (mode == PanelMode::Default) ? track.track : nullptr;
|
||||||
}) | rpl::distinct_until_changed(
|
}) | rpl::distinct_until_changed(
|
||||||
) | rpl::start_with_next([=](Webrtc::VideoTrack *track) {
|
) | rpl::start_with_next([=](Webrtc::VideoTrack *track) {
|
||||||
_pinnedTrackLifetime.destroy();
|
_pinnedTrackLifetime.destroy();
|
||||||
|
|
|
@ -26,6 +26,7 @@ class GroupCall;
|
||||||
|
|
||||||
namespace Calls::Group {
|
namespace Calls::Group {
|
||||||
|
|
||||||
|
class MembersRow;
|
||||||
struct VolumeRequest;
|
struct VolumeRequest;
|
||||||
struct MuteRequest;
|
struct MuteRequest;
|
||||||
enum class PanelMode;
|
enum class PanelMode;
|
||||||
|
@ -37,6 +38,7 @@ public:
|
||||||
Members(
|
Members(
|
||||||
not_null<QWidget*> parent,
|
not_null<QWidget*> parent,
|
||||||
not_null<GroupCall*> call);
|
not_null<GroupCall*> call);
|
||||||
|
~Members();
|
||||||
|
|
||||||
[[nodiscard]] int desiredHeight() const;
|
[[nodiscard]] int desiredHeight() const;
|
||||||
[[nodiscard]] rpl::producer<int> desiredHeightValue() const override;
|
[[nodiscard]] rpl::producer<int> desiredHeightValue() const override;
|
||||||
|
@ -51,9 +53,12 @@ public:
|
||||||
return _addMemberRequests.events();
|
return _addMemberRequests.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MembersRow *lookupRow(not_null<PeerData*> peer) const;
|
||||||
|
|
||||||
void setMode(PanelMode mode);
|
void setMode(PanelMode mode);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
class Controller;
|
||||||
using ListWidget = PeerListContent;
|
using ListWidget = PeerListContent;
|
||||||
|
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
@ -84,7 +89,7 @@ private:
|
||||||
const not_null<GroupCall*> _call;
|
const not_null<GroupCall*> _call;
|
||||||
rpl::variable<PanelMode> _mode = PanelMode();
|
rpl::variable<PanelMode> _mode = PanelMode();
|
||||||
object_ptr<Ui::ScrollArea> _scroll;
|
object_ptr<Ui::ScrollArea> _scroll;
|
||||||
std::unique_ptr<PeerListController> _listController;
|
std::unique_ptr<Controller> _listController;
|
||||||
not_null<Ui::VerticalLayout*> _layout;
|
not_null<Ui::VerticalLayout*> _layout;
|
||||||
const not_null<Ui::RpWidget*> _pinnedVideo;
|
const not_null<Ui::RpWidget*> _pinnedVideo;
|
||||||
rpl::variable<Ui::RpWidget*> _addMemberButton = nullptr;
|
rpl::variable<Ui::RpWidget*> _addMemberButton = nullptr;
|
||||||
|
|
|
@ -495,20 +495,27 @@ void MembersRow::paintScaledUserpic(
|
||||||
_blobsAnimation->userpicCache);
|
_blobsAnimation->userpicCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MembersRow::paintMuteIcon(
|
||||||
|
Painter &p,
|
||||||
|
QRect iconRect,
|
||||||
|
MembersRowStyle style) {
|
||||||
|
_delegate->rowPaintIcon(p, iconRect, computeIconState(style));
|
||||||
|
}
|
||||||
|
|
||||||
void MembersRow::paintNarrowName(
|
void MembersRow::paintNarrowName(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
int x,
|
int x,
|
||||||
int y,
|
int y,
|
||||||
int sizew,
|
int sizew,
|
||||||
int sizeh,
|
int sizeh,
|
||||||
NarrowStyle style) {
|
MembersRowStyle style) {
|
||||||
if (_narrowName.isEmpty()) {
|
if (_narrowName.isEmpty()) {
|
||||||
_narrowName.setText(
|
_narrowName.setText(
|
||||||
st::semiboldTextStyle,
|
st::semiboldTextStyle,
|
||||||
generateShortName(),
|
generateShortName(),
|
||||||
Ui::NameTextOptions());
|
Ui::NameTextOptions());
|
||||||
}
|
}
|
||||||
if (style == NarrowStyle::Video) {
|
if (style == MembersRowStyle::Video) {
|
||||||
_delegate->rowPaintNarrowShadow(p, x, y, sizew, sizeh);
|
_delegate->rowPaintNarrowShadow(p, x, y, sizew, sizeh);
|
||||||
}
|
}
|
||||||
const auto &icon = st::groupCallVideoCrossLine.icon;
|
const auto &icon = st::groupCallVideoCrossLine.icon;
|
||||||
|
@ -525,7 +532,7 @@ void MembersRow::paintNarrowName(
|
||||||
_delegate->rowPaintIcon(p, iconRect, state);
|
_delegate->rowPaintIcon(p, iconRect, state);
|
||||||
|
|
||||||
p.setPen([&] {
|
p.setPen([&] {
|
||||||
if (style == NarrowStyle::Video) {
|
if (style == MembersRowStyle::Video) {
|
||||||
return st::groupCallVideoTextFg->p;
|
return st::groupCallVideoTextFg->p;
|
||||||
} else if (state.speaking == 1. && !state.mutedByMe) {
|
} else if (state.speaking == 1. && !state.mutedByMe) {
|
||||||
return st::groupCallMemberActiveIcon->p;
|
return st::groupCallMemberActiveIcon->p;
|
||||||
|
@ -580,7 +587,7 @@ void MembersRow::paintComplexUserpic(
|
||||||
bool selected) {
|
bool selected) {
|
||||||
if (mode == PanelMode::Wide) {
|
if (mode == PanelMode::Wide) {
|
||||||
if (paintVideo(p, x, y, sizew, sizeh, mode)) {
|
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);
|
_delegate->rowPaintNarrowBorder(p, x, y, this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -602,7 +609,7 @@ void MembersRow::paintComplexUserpic(
|
||||||
sizeh,
|
sizeh,
|
||||||
mode);
|
mode);
|
||||||
if (mode == PanelMode::Wide) {
|
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);
|
_delegate->rowPaintNarrowBorder(p, x, y, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -699,6 +706,26 @@ void MembersRow::paintStatusText(
|
||||||
int availableWidth,
|
int availableWidth,
|
||||||
int outerWidth,
|
int outerWidth,
|
||||||
bool selected) {
|
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 &font = st::normalFont;
|
||||||
const auto about = (_state == State::Inactive
|
const auto about = (_state == State::Inactive
|
||||||
|| _state == State::Muted
|
|| _state == State::Muted
|
||||||
|
@ -727,7 +754,9 @@ void MembersRow::paintStatusText(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
p.setFont(font);
|
p.setFont(font);
|
||||||
if (_state == State::MutedByMe) {
|
if (style == MembersRowStyle::LargeVideo) {
|
||||||
|
p.setPen(st::groupCallVideoSubTextFg);
|
||||||
|
} else if (_state == State::MutedByMe) {
|
||||||
p.setPen(st::groupCallMemberMutedIcon);
|
p.setPen(st::groupCallMemberMutedIcon);
|
||||||
} else {
|
} else {
|
||||||
p.setPen(st::groupCallMemberNotJoinedStatus);
|
p.setPen(st::groupCallMemberNotJoinedStatus);
|
||||||
|
@ -738,7 +767,7 @@ void MembersRow::paintStatusText(
|
||||||
outerWidth,
|
outerWidth,
|
||||||
(_state == State::MutedByMe
|
(_state == State::MutedByMe
|
||||||
? tr::lng_group_call_muted_by_me_status(tr::now)
|
? tr::lng_group_call_muted_by_me_status(tr::now)
|
||||||
: !about.isEmpty()
|
: (!about.isEmpty() && style != MembersRowStyle::LargeVideo)
|
||||||
? font->m.elidedText(about, Qt::ElideRight, availableWidth)
|
? font->m.elidedText(about, Qt::ElideRight, availableWidth)
|
||||||
: _delegate->rowIsMe(peer())
|
: _delegate->rowIsMe(peer())
|
||||||
? tr::lng_status_connecting(tr::now)
|
? tr::lng_status_connecting(tr::now)
|
||||||
|
@ -797,11 +826,11 @@ void MembersRow::paintAction(
|
||||||
_actionRipple.reset();
|
_actionRipple.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_delegate->rowPaintIcon(p, iconRect, computeIconState());
|
paintMuteIcon(p, iconRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
MembersRowDelegate::IconState MembersRow::computeIconState(
|
MembersRowDelegate::IconState MembersRow::computeIconState(
|
||||||
NarrowStyle style) const {
|
MembersRowStyle style) const {
|
||||||
const auto speaking = _speakingAnimation.value(_speaking ? 1. : 0.);
|
const auto speaking = _speakingAnimation.value(_speaking ? 1. : 0.);
|
||||||
const auto active = _activeAnimation.value(
|
const auto active = _activeAnimation.value(
|
||||||
(_state == State::Active) ? 1. : 0.);
|
(_state == State::Active) ? 1. : 0.);
|
||||||
|
@ -814,7 +843,7 @@ MembersRowDelegate::IconState MembersRow::computeIconState(
|
||||||
.muted = muted,
|
.muted = muted,
|
||||||
.mutedByMe = (_state == State::MutedByMe),
|
.mutedByMe = (_state == State::MutedByMe),
|
||||||
.raisedHand = (_state == State::RaisedHand),
|
.raisedHand = (_state == State::RaisedHand),
|
||||||
.narrowStyle = style,
|
.style = style,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,10 +27,11 @@ class RippleAnimation;
|
||||||
|
|
||||||
namespace Calls::Group {
|
namespace Calls::Group {
|
||||||
|
|
||||||
enum class NarrowStyle {
|
enum class MembersRowStyle {
|
||||||
None,
|
None,
|
||||||
Userpic,
|
Userpic,
|
||||||
Video,
|
Video,
|
||||||
|
LargeVideo,
|
||||||
};
|
};
|
||||||
|
|
||||||
class MembersRow;
|
class MembersRow;
|
||||||
|
@ -42,7 +43,7 @@ public:
|
||||||
float64 muted = 0.;
|
float64 muted = 0.;
|
||||||
bool mutedByMe = false;
|
bool mutedByMe = false;
|
||||||
bool raisedHand = false;
|
bool raisedHand = false;
|
||||||
NarrowStyle narrowStyle = NarrowStyle::None;
|
MembersRowStyle style = MembersRowStyle::None;
|
||||||
};
|
};
|
||||||
virtual bool rowIsMe(not_null<PeerData*> participantPeer) = 0;
|
virtual bool rowIsMe(not_null<PeerData*> participantPeer) = 0;
|
||||||
virtual bool rowCanMuteMembers() = 0;
|
virtual bool rowCanMuteMembers() = 0;
|
||||||
|
@ -155,6 +156,19 @@ public:
|
||||||
int availableWidth,
|
int availableWidth,
|
||||||
int outerWidth,
|
int outerWidth,
|
||||||
bool selected) override;
|
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:
|
private:
|
||||||
struct BlobsAnimation;
|
struct BlobsAnimation;
|
||||||
|
@ -211,9 +225,9 @@ private:
|
||||||
int y,
|
int y,
|
||||||
int sizew,
|
int sizew,
|
||||||
int sizeh,
|
int sizeh,
|
||||||
NarrowStyle style);
|
MembersRowStyle style);
|
||||||
[[nodiscard]] MembersRowDelegate::IconState computeIconState(
|
[[nodiscard]] MembersRowDelegate::IconState computeIconState(
|
||||||
NarrowStyle style = NarrowStyle::None) const;
|
MembersRowStyle style = MembersRowStyle::None) const;
|
||||||
|
|
||||||
const not_null<MembersRowDelegate*> _delegate;
|
const not_null<MembersRowDelegate*> _delegate;
|
||||||
State _state = State::Inactive;
|
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_members.h"
|
||||||
#include "calls/group/calls_group_settings.h"
|
#include "calls/group/calls_group_settings.h"
|
||||||
#include "calls/group/calls_group_menu.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 "calls/group/ui/desktop_capture_choose_source.h"
|
||||||
#include "ui/platform/ui_platform_window_title.h"
|
#include "ui/platform/ui_platform_window_title.h"
|
||||||
#include "ui/platform/ui_platform_utility.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 "base/timer_rpl.h"
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
#include "apiwrap.h" // api().kickParticipant.
|
#include "apiwrap.h" // api().kickParticipant.
|
||||||
#include "media/view/media_view_pip.h"
|
|
||||||
#include "webrtc/webrtc_video_track.h"
|
#include "webrtc/webrtc_video_track.h"
|
||||||
#include "styles/style_calls.h"
|
#include "styles/style_calls.h"
|
||||||
#include "styles/style_layers.h"
|
#include "styles/style_layers.h"
|
||||||
|
@ -994,89 +994,24 @@ void Panel::raiseControls() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::setupPinnedVideo() {
|
void Panel::setupPinnedVideo() {
|
||||||
_pinnedVideo.create(widget());
|
auto track = _call->videoLargeTrackValue(
|
||||||
_pinnedVideo->setVisible(_mode == PanelMode::Wide);
|
) | rpl::map([=](GroupCall::LargeTrack track) {
|
||||||
_pinnedVideo->setAttribute(Qt::WA_OpaquePaintEvent, true);
|
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();
|
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() {
|
void Panel::setupJoinAsChangedToasts() {
|
||||||
|
|
|
@ -54,6 +54,7 @@ struct CallBodyLayout;
|
||||||
namespace Calls::Group {
|
namespace Calls::Group {
|
||||||
|
|
||||||
class Members;
|
class Members;
|
||||||
|
class LargeVideo;
|
||||||
enum class PanelMode;
|
enum class PanelMode;
|
||||||
|
|
||||||
class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate {
|
class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate {
|
||||||
|
@ -141,7 +142,7 @@ private:
|
||||||
object_ptr<Ui::DropdownMenu> _menu = { nullptr };
|
object_ptr<Ui::DropdownMenu> _menu = { nullptr };
|
||||||
object_ptr<Ui::AbstractButton> _joinAsToggle = { nullptr };
|
object_ptr<Ui::AbstractButton> _joinAsToggle = { nullptr };
|
||||||
object_ptr<Members> _members = { nullptr };
|
object_ptr<Members> _members = { nullptr };
|
||||||
object_ptr<Ui::RpWidget> _pinnedVideo = { nullptr };
|
std::unique_ptr<LargeVideo> _pinnedVideo;
|
||||||
rpl::lifetime _pinnedTrackLifetime;
|
rpl::lifetime _pinnedTrackLifetime;
|
||||||
object_ptr<Ui::FlatLabel> _startsIn = { nullptr };
|
object_ptr<Ui::FlatLabel> _startsIn = { nullptr };
|
||||||
object_ptr<Ui::RpWidget> _countdown = { 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
|