mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 22:54:01 +02:00
Support mode switching in single widget Viewport.
This commit is contained in:
parent
3edb2d08ba
commit
b864563f47
12 changed files with 303 additions and 1244 deletions
|
@ -277,8 +277,6 @@ 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
|
||||
|
|
|
@ -1,907 +0,0 @@
|
|||
/*
|
||||
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_viewport.h" // GenerateShadow.
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "calls/group/calls_group_members_row.h"
|
||||
#include "media/view/media_view_pip.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/abstract_button.h"
|
||||
#include "ui/gl/gl_surface.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/cross_line.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtGui/QOpenGLShader>
|
||||
#include <QtGui/QOpenGLShaderProgram>
|
||||
#include <QtGui/QOpenGLBuffer>
|
||||
|
||||
namespace Calls::Group {
|
||||
namespace {
|
||||
|
||||
constexpr auto kShadowMaxAlpha = 80;
|
||||
|
||||
struct ShaderPart {
|
||||
QString header;
|
||||
QString body;
|
||||
};
|
||||
|
||||
[[nodiscard]] QString VertexShader(const std::vector<ShaderPart> &parts) {
|
||||
const auto accumulate = [&](auto proj) {
|
||||
return ranges::accumulate(parts, QString(), std::plus<>(), proj);
|
||||
};
|
||||
return R"(
|
||||
#version 130
|
||||
in vec2 position;
|
||||
)" + accumulate(&ShaderPart::header) + R"(
|
||||
void main() {
|
||||
vec4 result = vec4(position, 0., 1.);
|
||||
)" + accumulate(&ShaderPart::body) + R"(
|
||||
gl_Position = result;
|
||||
}
|
||||
)";
|
||||
}
|
||||
|
||||
[[nodiscard]] QString FragmentShader(const std::vector<ShaderPart> &parts) {
|
||||
const auto accumulate = [&](auto proj) {
|
||||
return ranges::accumulate(parts, QString(), std::plus<>(), proj);
|
||||
};
|
||||
return R"(
|
||||
#version 130
|
||||
out vec4 fragColor;
|
||||
)" + accumulate(&ShaderPart::header) + R"(
|
||||
void main() {
|
||||
vec4 result = vec4(0., 0., 0., 0.);
|
||||
)" + accumulate(&ShaderPart::body) + R"(
|
||||
fragColor = result;
|
||||
}
|
||||
)";
|
||||
}
|
||||
|
||||
[[nodiscard]] ShaderPart VertexPassTextureCoord() {
|
||||
return {
|
||||
.header = R"(
|
||||
in vec2 texcoord;
|
||||
out vec2 v_texcoord;
|
||||
)",
|
||||
.body = R"(
|
||||
v_texcoord = texcoord;
|
||||
)",
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] ShaderPart FragmentSampleTexture() {
|
||||
return {
|
||||
.header = R"(
|
||||
in vec2 v_texcoord;
|
||||
uniform sampler2D s_texture;
|
||||
)",
|
||||
.body = R"(
|
||||
result = texture(s_texture, v_texcoord);
|
||||
result = vec4(result.b, result.g, result.r, result.a);
|
||||
)",
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] ShaderPart VertexViewportTransform() {
|
||||
return {
|
||||
.header = R"(
|
||||
uniform vec2 viewport;
|
||||
vec4 transform(vec4 position) {
|
||||
return vec4(
|
||||
vec2(-1, -1) + 2 * position.xy / viewport,
|
||||
position.z,
|
||||
position.w);
|
||||
}
|
||||
)",
|
||||
.body = R"(
|
||||
result = transform(result);
|
||||
)",
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] ShaderPart FragmentRoundCorners() {
|
||||
return {
|
||||
.header = R"(
|
||||
uniform vec2 viewport;
|
||||
uniform float roundRadius;
|
||||
float roundedCorner() {
|
||||
vec2 viewportHalf = viewport / 2;
|
||||
vec2 fromViewportCenter = abs(gl_FragCoord.xy - viewportHalf);
|
||||
vec2 vectorRadius = vec2(roundRadius + 0.5, roundRadius + 0.5);
|
||||
vec2 fromCenterWithRadius = fromViewportCenter + vectorRadius;
|
||||
vec2 fromRoundingCenter = max(fromCenterWithRadius, viewportHalf)
|
||||
- viewportHalf;
|
||||
float d = length(fromRoundingCenter) - roundRadius;
|
||||
return 1. - smoothstep(0., 1., d);
|
||||
}
|
||||
)",
|
||||
.body = R"(
|
||||
result = vec4(result.r, result.g, result.b, result.a * roundedCorner());
|
||||
)",
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] ShaderPart FragmentStaticColor() {
|
||||
return {
|
||||
.header = R"(
|
||||
uniform vec4 s_color;
|
||||
)",
|
||||
.body = R"(
|
||||
result = s_color;
|
||||
)",
|
||||
};
|
||||
}
|
||||
|
||||
not_null<QOpenGLShader*> MakeShader(
|
||||
not_null<QOpenGLShaderProgram*> program,
|
||||
QOpenGLShader::ShaderType type,
|
||||
const QString &source) {
|
||||
const auto result = new QOpenGLShader(type, program);
|
||||
if (!result->compileSourceCode(source)) {
|
||||
LOG(("Shader Compilation Failed: %1, error %2."
|
||||
).arg(source
|
||||
).arg(result->log()));
|
||||
}
|
||||
program->addShader(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void LinkProgram(
|
||||
not_null<QOpenGLShaderProgram*> program,
|
||||
const QString &vertexSource,
|
||||
const QString &fragmentSource) {
|
||||
MakeShader(program, QOpenGLShader::Vertex, vertexSource);
|
||||
MakeShader(program, QOpenGLShader::Fragment, fragmentSource);
|
||||
if (!program->link()) {
|
||||
LOG(("Shader Link Failed: %1.").arg(program->log()));
|
||||
}
|
||||
}
|
||||
|
||||
class Quads final {
|
||||
public:
|
||||
void fill(QRect rect);
|
||||
void paint(
|
||||
not_null<QOpenGLFunctions*> f,
|
||||
not_null<QOpenGLBuffer*> buffer,
|
||||
not_null<QOpenGLShaderProgram*> program,
|
||||
QSize viewport,
|
||||
const QColor &color,
|
||||
Fn<void()> additional = nullptr);
|
||||
|
||||
private:
|
||||
static constexpr auto kMaxTriangles = 8;
|
||||
std::array<GLfloat, 6 * kMaxTriangles> coordinates{ 0 };
|
||||
int triangles = 0;
|
||||
|
||||
};
|
||||
|
||||
void Quads::fill(QRect rect) {
|
||||
Expects(triangles + 2 <= kMaxTriangles);
|
||||
|
||||
auto i = triangles * 6;
|
||||
coordinates[i + 0] = coordinates[i + 10] = rect.x();
|
||||
coordinates[i + 1] = coordinates[i + 11] = rect.y();
|
||||
coordinates[i + 2] = rect.x() + rect.width();
|
||||
coordinates[i + 3] = rect.y();
|
||||
coordinates[i + 4] = coordinates[i + 6] = rect.x() + rect.width();
|
||||
coordinates[i + 5] = coordinates[i + 7] = rect.y() + rect.height();
|
||||
coordinates[i + 8] = rect.x();
|
||||
coordinates[i + 9] = rect.y() + rect.height();
|
||||
triangles += 2;
|
||||
}
|
||||
|
||||
void Quads::paint(
|
||||
not_null<QOpenGLFunctions*> f,
|
||||
not_null<QOpenGLBuffer*> buffer,
|
||||
not_null<QOpenGLShaderProgram*> program,
|
||||
QSize viewport,
|
||||
const QColor &color,
|
||||
Fn<void()> additional) {
|
||||
if (!triangles) {
|
||||
return;
|
||||
}
|
||||
buffer->bind();
|
||||
buffer->allocate(coordinates.data(), triangles * 6 * sizeof(GLfloat));
|
||||
|
||||
f->glUseProgram(program->programId());
|
||||
program->setUniformValue("viewport", QSizeF(viewport));
|
||||
program->setUniformValue("s_color", QVector4D(
|
||||
color.redF(),
|
||||
color.greenF(),
|
||||
color.blueF(),
|
||||
color.alphaF()));
|
||||
|
||||
GLint position = program->attributeLocation("position");
|
||||
f->glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (void*)0);
|
||||
f->glEnableVertexAttribArray(position);
|
||||
|
||||
if (additional) {
|
||||
additional();
|
||||
}
|
||||
|
||||
f->glDrawArrays(GL_TRIANGLES, 0, triangles * 3);
|
||||
|
||||
f->glDisableVertexAttribArray(position);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct LargeVideo::PinButton {
|
||||
PinButton(
|
||||
not_null<QWidget*> parent,
|
||||
const style::GroupCallLargeVideo &st);
|
||||
|
||||
Ui::AbstractButton area;
|
||||
Ui::CrossLineAnimation icon;
|
||||
Ui::RoundRect background;
|
||||
Ui::Text::String text;
|
||||
QRect rect;
|
||||
Ui::Animations::Simple shownAnimation;
|
||||
bool shown = false;
|
||||
};
|
||||
|
||||
class LargeVideo::RendererGL : public Ui::GL::Renderer {
|
||||
public:
|
||||
explicit RendererGL(not_null<LargeVideo*> owner) : _owner(owner) {
|
||||
}
|
||||
~RendererGL();
|
||||
|
||||
void init(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
not_null<QOpenGLFunctions*> f) override;
|
||||
|
||||
void deinit(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
not_null<QOpenGLFunctions*> f) override;
|
||||
|
||||
void resize(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
not_null<QOpenGLFunctions*> f,
|
||||
int w,
|
||||
int h) override;
|
||||
|
||||
void paint(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
not_null<QOpenGLFunctions*> f) override;
|
||||
|
||||
private:
|
||||
const not_null<LargeVideo*> _owner;
|
||||
|
||||
std::array<GLuint, 3> _textures = {};
|
||||
std::optional<QOpenGLBuffer> _frameBuffer;
|
||||
std::optional<QOpenGLBuffer> _fillBuffer;
|
||||
std::optional<QOpenGLBuffer> _cornersBgBuffer;
|
||||
std::optional<QOpenGLShaderProgram> _frameProgram;
|
||||
std::optional<QOpenGLShaderProgram> _fillProgram;
|
||||
std::optional<QOpenGLShaderProgram> _cornersBgProgram;
|
||||
qint64 _frameKey = 0;
|
||||
|
||||
};
|
||||
|
||||
LargeVideo::PinButton::PinButton(
|
||||
not_null<QWidget*> parent,
|
||||
const style::GroupCallLargeVideo &st)
|
||||
: area(parent)
|
||||
, icon(st::groupCallLargeVideoPin)
|
||||
, background(
|
||||
(st.pinPadding.top()
|
||||
+ st::groupCallLargeVideoPin.icon.height()
|
||||
+ st.pinPadding.bottom()) / 2,
|
||||
st::radialBg) {
|
||||
}
|
||||
|
||||
LargeVideo::RendererGL::~RendererGL() {
|
||||
}
|
||||
|
||||
void LargeVideo::RendererGL::init(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
not_null<QOpenGLFunctions*> f) {
|
||||
f->glGenTextures(3, _textures.data());
|
||||
for (const auto texture : _textures) {
|
||||
f->glBindTexture(GL_TEXTURE_2D, texture);
|
||||
const auto clamp = GL_CLAMP_TO_EDGE;
|
||||
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp);
|
||||
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp);
|
||||
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
}
|
||||
_frameBuffer.emplace();
|
||||
_frameBuffer->setUsagePattern(QOpenGLBuffer::StaticDraw);
|
||||
_frameBuffer->create();
|
||||
_frameProgram.emplace();
|
||||
LinkProgram(
|
||||
&*_frameProgram,
|
||||
VertexShader({
|
||||
VertexViewportTransform(),
|
||||
VertexPassTextureCoord(),
|
||||
}),
|
||||
FragmentShader({
|
||||
FragmentSampleTexture(),
|
||||
FragmentRoundCorners(),
|
||||
}));
|
||||
|
||||
_fillBuffer.emplace();
|
||||
_fillBuffer->setUsagePattern(QOpenGLBuffer::StaticDraw);
|
||||
_fillBuffer->create();
|
||||
_fillProgram.emplace();
|
||||
LinkProgram(
|
||||
&*_fillProgram,
|
||||
VertexShader({ VertexViewportTransform() }),
|
||||
FragmentShader({
|
||||
FragmentStaticColor(),
|
||||
FragmentRoundCorners(),
|
||||
}));
|
||||
|
||||
|
||||
_cornersBgBuffer.emplace();
|
||||
_cornersBgBuffer->setUsagePattern(QOpenGLBuffer::StaticDraw);
|
||||
_cornersBgBuffer->create();
|
||||
_cornersBgProgram.emplace();
|
||||
LinkProgram(
|
||||
&*_cornersBgProgram,
|
||||
VertexShader({ VertexViewportTransform() }),
|
||||
FragmentShader({ FragmentStaticColor() }));
|
||||
}
|
||||
|
||||
void LargeVideo::RendererGL::deinit(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
not_null<QOpenGLFunctions*> f) {
|
||||
if (_textures.front()) {
|
||||
f->glDeleteTextures(_textures.size(), _textures.data());
|
||||
ranges::fill(_textures, 0);
|
||||
}
|
||||
_frameBuffer = std::nullopt;
|
||||
_fillBuffer = std::nullopt;
|
||||
_frameProgram = std::nullopt;
|
||||
_fillProgram = std::nullopt;
|
||||
_cornersBgProgram = std::nullopt;
|
||||
}
|
||||
|
||||
void LargeVideo::RendererGL::resize(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
not_null<QOpenGLFunctions*> f,
|
||||
int w,
|
||||
int h) {
|
||||
f->glViewport(0, 0, w, h);
|
||||
}
|
||||
|
||||
void LargeVideo::RendererGL::paint(
|
||||
not_null<QOpenGLWidget*> widget,
|
||||
not_null<QOpenGLFunctions*> f) {
|
||||
const auto size = _owner->widget()->size();
|
||||
if (size.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto bg = st::groupCallMembersBg->c;
|
||||
const auto bgvector = QVector4D(
|
||||
bg.redF(),
|
||||
bg.greenF(),
|
||||
bg.blueF(),
|
||||
bg.alphaF());
|
||||
|
||||
const auto data = _owner->_track
|
||||
? _owner->_track.track->frameWithInfo()
|
||||
: Webrtc::FrameWithInfo();
|
||||
const auto &image = data.original;
|
||||
const auto rotation = data.rotation;
|
||||
|
||||
f->glEnable(GL_BLEND);
|
||||
f->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
const auto scaled = Media::View::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 right = left + scaled.width();
|
||||
const auto bottom = top + scaled.height();
|
||||
const auto radius = GLfloat(st::roundRadiusLarge * cIntRetinaFactor());
|
||||
|
||||
auto cornersBg = Quads();
|
||||
const auto cornerBgSize = QSize(int(radius), int(radius));
|
||||
cornersBg.fill({ QPoint(), cornerBgSize });
|
||||
cornersBg.fill({ QPoint(size.width() - int(radius), 0), cornerBgSize });
|
||||
cornersBg.fill({
|
||||
QPoint(size.width() - int(radius), size.height() - int(radius)),
|
||||
cornerBgSize
|
||||
});
|
||||
cornersBg.fill({ QPoint(0, size.height() - int(radius)), cornerBgSize });
|
||||
cornersBg.paint(
|
||||
f,
|
||||
&*_cornersBgBuffer,
|
||||
&*_cornersBgProgram,
|
||||
size,
|
||||
st::groupCallBg->c);
|
||||
|
||||
auto sideQuads = Quads();
|
||||
if (!image.isNull()) {
|
||||
auto texcoords = std::array<std::array<GLfloat, 2>, 4> { {
|
||||
{ { 0, 1 }},
|
||||
{ { 1, 1 } },
|
||||
{ { 1, 0 } },
|
||||
{ { 0, 0 } },
|
||||
} };
|
||||
if (rotation > 0) {
|
||||
std::rotate(
|
||||
texcoords.begin(),
|
||||
texcoords.begin() + (rotation / 90),
|
||||
texcoords.end());
|
||||
}
|
||||
const GLfloat vertices[] = {
|
||||
float(left), float(top), texcoords[0][0], texcoords[0][1],
|
||||
float(right), float(top), texcoords[1][0], texcoords[1][1],
|
||||
float(right), float(bottom), texcoords[2][0], texcoords[2][1],
|
||||
float(left), float(bottom), texcoords[3][0], texcoords[3][1],
|
||||
};
|
||||
|
||||
f->glUseProgram(_frameProgram->programId());
|
||||
f->glActiveTexture(GL_TEXTURE0);
|
||||
f->glBindTexture(GL_TEXTURE_2D, _textures[0]);
|
||||
const auto key = image.cacheKey();
|
||||
if (_frameKey != key) {
|
||||
_frameKey = key;
|
||||
f->glPixelStorei(GL_UNPACK_ROW_LENGTH, image.bytesPerLine() / 4);
|
||||
f->glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RGB,
|
||||
image.width(),
|
||||
image.height(),
|
||||
0,
|
||||
GL_RGBA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
image.constBits());
|
||||
f->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
}
|
||||
_owner->_track.track->markFrameShown();
|
||||
|
||||
_frameBuffer->bind();
|
||||
_frameBuffer->allocate(vertices, sizeof(vertices));
|
||||
|
||||
_frameProgram->setUniformValue("viewport", QSizeF(size));
|
||||
_frameProgram->setUniformValue("s_texture", GLint(0));
|
||||
_frameProgram->setUniformValue("roundRadius", radius);
|
||||
|
||||
GLint position = _frameProgram->attributeLocation("position");
|
||||
f->glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)0);
|
||||
f->glEnableVertexAttribArray(position);
|
||||
|
||||
GLint texcoord = _frameProgram->attributeLocation("texcoord");
|
||||
f->glVertexAttribPointer(texcoord, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)(2 * sizeof(GLfloat)));
|
||||
f->glEnableVertexAttribArray(texcoord);
|
||||
|
||||
f->glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
|
||||
f->glDisableVertexAttribArray(position);
|
||||
f->glDisableVertexAttribArray(texcoord);
|
||||
|
||||
if (left > 0) {
|
||||
sideQuads.fill({ 0, 0, left, size.height() });
|
||||
}
|
||||
if (right < size.width()) {
|
||||
sideQuads.fill({ right, 0, size.width() - right, size.height() });
|
||||
}
|
||||
if (top > 0) {
|
||||
sideQuads.fill({ 0, 0, size.width(), top });
|
||||
}
|
||||
if (bottom < size.height()) {
|
||||
sideQuads.fill({ 0, bottom, size.width(), size.height() - bottom });
|
||||
}
|
||||
} else {
|
||||
sideQuads.fill({ 0, 0, size.width(), size.height() });
|
||||
}
|
||||
sideQuads.paint(
|
||||
f,
|
||||
&*_fillBuffer,
|
||||
&*_fillProgram,
|
||||
size,
|
||||
st::groupCallMembersBg->c,
|
||||
[&] { _fillProgram->setUniformValue("roundRadius", radius); });
|
||||
}
|
||||
|
||||
LargeVideo::LargeVideo(
|
||||
QWidget *parent,
|
||||
const style::GroupCallLargeVideo &st,
|
||||
bool visible,
|
||||
rpl::producer<LargeVideoTrack> track,
|
||||
rpl::producer<bool> pinned)
|
||||
: _content(Ui::GL::CreateSurface(
|
||||
parent,
|
||||
[=](Ui::GL::Capabilities capabilities) {
|
||||
return chooseRenderer(capabilities);
|
||||
}))
|
||||
, _st(st)
|
||||
, _pinButton((_st.pinPosition.x() >= 0)
|
||||
? std::make_unique<PinButton>(widget(), st)
|
||||
: nullptr)
|
||||
, _smallLayout(!_pinButton) {
|
||||
widget()->resize(0, 0);
|
||||
widget()->setVisible(visible);
|
||||
if (_smallLayout) {
|
||||
widget()->setCursor(style::cur_pointer);
|
||||
}
|
||||
setup(std::move(track), std::move(pinned));
|
||||
}
|
||||
|
||||
LargeVideo::~LargeVideo() = default;
|
||||
|
||||
Ui::GL::ChosenRenderer LargeVideo::chooseRenderer(
|
||||
Ui::GL::Capabilities capabilities) {
|
||||
class Renderer : public Ui::GL::Renderer {
|
||||
public:
|
||||
explicit Renderer(not_null<LargeVideo*> owner) : _owner(owner) {
|
||||
}
|
||||
|
||||
void paintFallback(
|
||||
Painter &&p,
|
||||
const QRegion &clip,
|
||||
Ui::GL::Backend backend) override {
|
||||
_owner->paint(
|
||||
p,
|
||||
clip.boundingRect(),
|
||||
backend == Ui::GL::Backend::OpenGL);
|
||||
}
|
||||
|
||||
private:
|
||||
const not_null<LargeVideo*> _owner;
|
||||
|
||||
};
|
||||
|
||||
const auto use = Platform::IsMac()
|
||||
? true
|
||||
: Platform::IsWindows()
|
||||
? capabilities.supported
|
||||
: capabilities.transparency;
|
||||
LOG(("OpenGL: %1 (LargeVideo)").arg(Logs::b(use)));
|
||||
if (use) {
|
||||
return {
|
||||
.renderer = std::make_unique<RendererGL>(this),
|
||||
.backend = Ui::GL::Backend::OpenGL,
|
||||
};
|
||||
}
|
||||
return {
|
||||
.renderer = std::make_unique<Renderer>(this),
|
||||
.backend = Ui::GL::Backend::Raster,
|
||||
};
|
||||
}
|
||||
|
||||
void LargeVideo::raise() {
|
||||
widget()->raise();
|
||||
}
|
||||
|
||||
void LargeVideo::setVisible(bool visible) {
|
||||
widget()->setVisible(visible);
|
||||
}
|
||||
|
||||
void LargeVideo::setGeometry(int x, int y, int width, int height) {
|
||||
widget()->setGeometry(x, y, width, height);
|
||||
if (width > 0 && height > 0) {
|
||||
const auto kMedium = style::ConvertScale(380);
|
||||
const auto kSmall = style::ConvertScale(200);
|
||||
_requestedQuality = (width > kMedium && height > kMedium)
|
||||
? VideoQuality::Full
|
||||
: (width > kSmall && height > kSmall)
|
||||
? VideoQuality::Medium
|
||||
: VideoQuality::Thumbnail;
|
||||
}
|
||||
}
|
||||
|
||||
void LargeVideo::setControlsShown(float64 shown) {
|
||||
if (_controlsShownRatio == shown) {
|
||||
return;
|
||||
}
|
||||
_controlsShownRatio = shown;
|
||||
widget()->update();
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
rpl::producer<bool> LargeVideo::pinToggled() const {
|
||||
return _pinButton
|
||||
? _pinButton->area.clicks() | rpl::map([=] { return !_pinned; })
|
||||
: rpl::never<bool>() | rpl::type_erased();
|
||||
}
|
||||
|
||||
QSize LargeVideo::trackSize() const {
|
||||
return _trackSize.current();
|
||||
}
|
||||
|
||||
rpl::producer<QSize> LargeVideo::trackSizeValue() const {
|
||||
return _trackSize.value();
|
||||
}
|
||||
|
||||
rpl::producer<VideoQuality> LargeVideo::requestedQuality() const {
|
||||
using namespace rpl::mappers;
|
||||
return rpl::combine(
|
||||
_content->shownValue(),
|
||||
_requestedQuality.value()
|
||||
) | rpl::filter([=](bool shown, auto) {
|
||||
return shown;
|
||||
}) | rpl::map(_2);
|
||||
}
|
||||
|
||||
rpl::lifetime &LargeVideo::lifetime() {
|
||||
return _content->lifetime();
|
||||
}
|
||||
|
||||
not_null<QWidget*> LargeVideo::widget() const {
|
||||
return _content->rpWidget();
|
||||
}
|
||||
|
||||
void LargeVideo::setup(
|
||||
rpl::producer<LargeVideoTrack> track,
|
||||
rpl::producer<bool> pinned) {
|
||||
widget()->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
_content->events(
|
||||
) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||
const auto type = e->type();
|
||||
if (type == QEvent::Enter && _pinButton) {
|
||||
togglePinShown(true);
|
||||
} else if (type == QEvent::Leave && _pinButton) {
|
||||
togglePinShown(false);
|
||||
} else if (type == QEvent::MouseButtonPress
|
||||
&& static_cast<QMouseEvent*>(
|
||||
e.get())->button() == Qt::LeftButton) {
|
||||
_mouseDown = true;
|
||||
} else if (type == QEvent::MouseButtonRelease
|
||||
&& static_cast<QMouseEvent*>(
|
||||
e.get())->button() == Qt::LeftButton
|
||||
&& _mouseDown) {
|
||||
_mouseDown = false;
|
||||
if (!widget()->isHidden()) {
|
||||
_clicks.fire({});
|
||||
}
|
||||
}
|
||||
}, _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;
|
||||
widget()->update();
|
||||
|
||||
_trackLifetime.destroy();
|
||||
if (!track.track) {
|
||||
_trackSize = QSize();
|
||||
return;
|
||||
}
|
||||
track.track->renderNextFrame(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto size = track.track->frameSize();
|
||||
if (size.isEmpty()) {
|
||||
track.track->markFrameShown();
|
||||
} else {
|
||||
_trackSize = size;
|
||||
}
|
||||
widget()->update();
|
||||
}, _trackLifetime);
|
||||
if (const auto size = track.track->frameSize(); !size.isEmpty()) {
|
||||
_trackSize = size;
|
||||
}
|
||||
}, _content->lifetime());
|
||||
|
||||
setupControls(std::move(pinned));
|
||||
}
|
||||
|
||||
void LargeVideo::togglePinShown(bool shown) {
|
||||
Expects(_pinButton != nullptr);
|
||||
|
||||
if (_pinButton->shown == shown) {
|
||||
return;
|
||||
}
|
||||
_pinButton->shown = shown;
|
||||
_pinButton->shownAnimation.start(
|
||||
[=] { updateControlsGeometry(); widget()->update(); },
|
||||
shown ? 0. : 1.,
|
||||
shown ? 1. : 0.,
|
||||
st::slideWrapDuration);
|
||||
}
|
||||
|
||||
void LargeVideo::setupControls(rpl::producer<bool> pinned) {
|
||||
std::move(pinned) | rpl::start_with_next([=](bool pinned) {
|
||||
_pinned = pinned;
|
||||
if (_pinButton) {
|
||||
_pinButton->text.setText(
|
||||
st::semiboldTextStyle,
|
||||
(pinned
|
||||
? tr::lng_pinned_unpin
|
||||
: tr::lng_pinned_pin)(tr::now));
|
||||
updateControlsGeometry();
|
||||
}
|
||||
widget()->update();
|
||||
}, _content->lifetime());
|
||||
|
||||
_content->sizeValue(
|
||||
) | rpl::start_with_next([=](QSize size) {
|
||||
updateControlsGeometry();
|
||||
}, _content->lifetime());
|
||||
}
|
||||
|
||||
void LargeVideo::updateControlsGeometry() {
|
||||
if (_pinButton) {
|
||||
const auto &icon = st::groupCallLargeVideoPin.icon;
|
||||
const auto innerWidth = icon.width()
|
||||
+ _st.pinTextPosition.x()
|
||||
+ _pinButton->text.maxWidth();
|
||||
const auto innerHeight = icon.height();
|
||||
const auto buttonWidth = _st.pinPadding.left() + innerWidth + _st.pinPadding.right();
|
||||
const auto buttonHeight = _st.pinPadding.top() + innerHeight + _st.pinPadding.bottom();
|
||||
const auto fullWidth = _st.pinPosition.x() * 2 + buttonWidth;
|
||||
const auto fullHeight = _st.pinPosition.y() * 2 + buttonHeight;
|
||||
const auto slide = anim::interpolate(
|
||||
_st.pinPosition.y() + buttonHeight,
|
||||
0,
|
||||
_pinButton->shownAnimation.value(_pinButton->shown ? 1. : 0.));
|
||||
_pinButton->rect = QRect(
|
||||
widget()->width() - _st.pinPosition.x() - buttonWidth,
|
||||
_st.pinPosition.y() - slide,
|
||||
buttonWidth,
|
||||
buttonHeight);
|
||||
_pinButton->area.setGeometry(
|
||||
widget()->width() - fullWidth,
|
||||
-slide,
|
||||
fullWidth,
|
||||
fullHeight);
|
||||
}
|
||||
}
|
||||
|
||||
void LargeVideo::paint(Painter &p, QRect clip, bool opengl) {
|
||||
const auto fill = [&](QRect rect) {
|
||||
if (rect.intersects(clip)) {
|
||||
p.fillRect(rect.intersected(clip), st::groupCallMembersBg);
|
||||
}
|
||||
};
|
||||
const auto data = _track
|
||||
? _track.track->frameWithInfo()
|
||||
: Webrtc::FrameWithInfo();
|
||||
const auto &image = data.original;
|
||||
const auto rotation = data.rotation;
|
||||
if (image.isNull()) {
|
||||
fill(clip);
|
||||
return;
|
||||
}
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
using namespace Media::View;
|
||||
const auto size = widget()->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, opengl)) {
|
||||
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();
|
||||
|
||||
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 = widget()->width();
|
||||
const auto height = widget()->height();
|
||||
|
||||
// Pin.
|
||||
if (_pinButton && _pinButton->rect.intersects(clip)) {
|
||||
const auto &icon = st::groupCallLargeVideoPin.icon;
|
||||
_pinButton->background.paint(p, _pinButton->rect);
|
||||
_pinButton->icon.paint(
|
||||
p,
|
||||
_pinButton->rect.marginsRemoved(_st.pinPadding).topLeft(),
|
||||
_pinned ? 1. : 0.);
|
||||
p.setPen(st::groupCallVideoTextFg);
|
||||
_pinButton->text.drawLeft(
|
||||
p,
|
||||
(_pinButton->rect.x()
|
||||
+ _st.pinPadding.left()
|
||||
+ icon.width()
|
||||
+ _st.pinTextPosition.x()),
|
||||
(_pinButton->rect.y()
|
||||
+ _st.pinPadding.top()
|
||||
+ _st.pinTextPosition.y()),
|
||||
_pinButton->text.maxWidth(),
|
||||
width);
|
||||
}
|
||||
|
||||
const auto fullShift = _st.namePosition.y() + st::normalFont->height;
|
||||
const auto shown = _controlsShownRatio;
|
||||
if (shown == 0.) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto shift = anim::interpolate(fullShift, 0, shown);
|
||||
|
||||
// Shadow.
|
||||
if (_shadow.isNull()) {
|
||||
_shadow = GenerateShadow(_st.shadowHeight, 0, kShadowMaxAlpha);
|
||||
}
|
||||
const auto shadowRect = QRect(
|
||||
0,
|
||||
(height - anim::interpolate(0, _st.shadowHeight, shown)),
|
||||
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);
|
||||
|
||||
// Mute.
|
||||
const auto &icon = st::groupCallLargeVideoCrossLine.icon;
|
||||
const auto iconLeft = width - _st.iconPosition.x() - icon.width();
|
||||
const auto iconTop = (height
|
||||
- _st.iconPosition.y()
|
||||
- icon.height()
|
||||
+ shift);
|
||||
_track.row->paintMuteIcon(
|
||||
p,
|
||||
{ iconLeft, iconTop, icon.width(), icon.height() },
|
||||
MembersRowStyle::LargeVideo);
|
||||
|
||||
// Name.
|
||||
p.setPen(st::groupCallVideoTextFg);
|
||||
const auto hasWidth = width
|
||||
- _st.iconPosition.x() - icon.width()
|
||||
- _st.namePosition.x();
|
||||
const auto nameLeft = _st.namePosition.x();
|
||||
const auto nameTop = (height
|
||||
- _st.namePosition.y()
|
||||
- st::semiboldFont->height
|
||||
+ shift);
|
||||
_track.row->name().drawLeftElided(p, nameLeft, nameTop, hasWidth, width);
|
||||
}
|
||||
|
||||
} // namespace Calls::Group
|
|
@ -1,115 +0,0 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
namespace style {
|
||||
struct GroupCallLargeVideo;
|
||||
} // namespace style
|
||||
|
||||
namespace Webrtc {
|
||||
class VideoTrack;
|
||||
} // namespace Webrtc
|
||||
|
||||
namespace Ui {
|
||||
class AbstractButton;
|
||||
class RpWidgetWrap;
|
||||
namespace GL {
|
||||
struct Capabilities;
|
||||
struct ChosenRenderer;
|
||||
} // namespace GL
|
||||
} // namespace Ui
|
||||
|
||||
namespace Calls::Group {
|
||||
|
||||
class MembersRow;
|
||||
enum class VideoQuality;
|
||||
|
||||
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);
|
||||
~LargeVideo();
|
||||
|
||||
void raise();
|
||||
void setVisible(bool visible);
|
||||
void setGeometry(int x, int y, int width, int height);
|
||||
void setControlsShown(float64 shown);
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> pinToggled() const;
|
||||
[[nodiscard]] rpl::producer<> clicks() const {
|
||||
return _clicks.events();
|
||||
}
|
||||
|
||||
[[nodiscard]] QSize trackSize() const;
|
||||
[[nodiscard]] rpl::producer<QSize> trackSizeValue() const;
|
||||
[[nodiscard]] rpl::producer<VideoQuality> requestedQuality() const;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
private:
|
||||
struct PinButton;
|
||||
class RendererGL;
|
||||
|
||||
[[nodiscard]] not_null<QWidget*> widget() const;
|
||||
|
||||
void setup(
|
||||
rpl::producer<LargeVideoTrack> track,
|
||||
rpl::producer<bool> pinned);
|
||||
void setupControls(rpl::producer<bool> pinned);
|
||||
void paint(Painter &p, QRect clip, bool opengl);
|
||||
void paintControls(Painter &p, QRect clip);
|
||||
void updateControlsGeometry();
|
||||
void togglePinShown(bool shown);
|
||||
|
||||
[[nodiscard]] Ui::GL::ChosenRenderer chooseRenderer(
|
||||
Ui::GL::Capabilities capabilities);
|
||||
|
||||
const std::unique_ptr<Ui::RpWidgetWrap> _content;
|
||||
const style::GroupCallLargeVideo &_st;
|
||||
LargeVideoTrack _track;
|
||||
QImage _shadow;
|
||||
std::unique_ptr<PinButton> _pinButton;
|
||||
rpl::event_stream<> _clicks;
|
||||
const bool _smallLayout = true;
|
||||
bool _pinned = false;
|
||||
bool _mouseDown = false;
|
||||
float64 _controlsShownRatio = 1.;
|
||||
rpl::variable<QSize> _trackSize;
|
||||
rpl::variable<VideoQuality> _requestedQuality;
|
||||
rpl::lifetime _trackLifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Calls::Group
|
|
@ -11,7 +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 "calls/group/calls_group_viewport.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_user.h"
|
||||
|
@ -46,11 +46,6 @@ using Row = MembersRow;
|
|||
|
||||
} // namespace
|
||||
|
||||
struct Members::VideoTile {
|
||||
std::unique_ptr<LargeVideo> video;
|
||||
VideoEndpoint endpoint;
|
||||
};
|
||||
|
||||
class Members::Controller final
|
||||
: public PeerListController
|
||||
, public MembersRowDelegate
|
||||
|
@ -58,7 +53,8 @@ class Members::Controller final
|
|||
public:
|
||||
Controller(
|
||||
not_null<GroupCall*> call,
|
||||
not_null<QWidget*> menuParent);
|
||||
not_null<QWidget*> menuParent,
|
||||
PanelMode mode);
|
||||
~Controller();
|
||||
|
||||
using MuteRequest = Group::MuteRequest;
|
||||
|
@ -226,11 +222,13 @@ private:
|
|||
|
||||
Members::Controller::Controller(
|
||||
not_null<GroupCall*> call,
|
||||
not_null<QWidget*> menuParent)
|
||||
not_null<QWidget*> menuParent,
|
||||
PanelMode mode)
|
||||
: _call(call)
|
||||
, _peer(call->peer())
|
||||
, _menuParent(menuParent)
|
||||
, _raisedHandStatusRemoveTimer([=] { scheduleRaisedHandStatusRemove(); })
|
||||
, _mode(mode)
|
||||
, _inactiveCrossLine(st::groupCallMemberInactiveCrossLine)
|
||||
, _coloredCrossLine(st::groupCallMemberColoredCrossLine)
|
||||
, _inactiveNarrowCrossLine(st::groupCallNarrowInactiveCrossLine)
|
||||
|
@ -1643,11 +1641,15 @@ std::unique_ptr<Row> Members::Controller::createInvitedRow(
|
|||
|
||||
Members::Members(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<GroupCall*> call)
|
||||
not_null<GroupCall*> call,
|
||||
not_null<Viewport*> viewport,
|
||||
PanelMode mode)
|
||||
: RpWidget(parent)
|
||||
, _call(call)
|
||||
, _viewport(viewport)
|
||||
, _mode(mode)
|
||||
, _scroll(this)
|
||||
, _listController(std::make_unique<Controller>(call, parent))
|
||||
, _listController(std::make_unique<Controller>(call, parent, mode))
|
||||
, _layout(_scroll->setOwnedWidget(
|
||||
object_ptr<Ui::VerticalLayout>(_scroll.data())))
|
||||
, _pinnedVideoWrap(_layout->add(object_ptr<Ui::RpWidget>(_layout.get()))) {
|
||||
|
@ -1656,7 +1658,7 @@ Members::Members(
|
|||
setContent(_list);
|
||||
setupFakeRoundCorners();
|
||||
_listController->setDelegate(static_cast<PeerListDelegate*>(this));
|
||||
setupPinnedVideo();
|
||||
grabViewport();
|
||||
}
|
||||
|
||||
Members::~Members() = default;
|
||||
|
@ -1799,10 +1801,8 @@ void Members::setMode(PanelMode mode) {
|
|||
if (_mode.current() == mode) {
|
||||
return;
|
||||
}
|
||||
grabViewport(mode);
|
||||
_mode = mode;
|
||||
for (const auto &tile : _videoTiles) {
|
||||
tile.video->setVisible(mode == PanelMode::Default);
|
||||
}
|
||||
_listController->setMode(mode);
|
||||
//_list->setMode((mode == PanelMode::Wide)
|
||||
// ? PeerListContent::Mode::Custom
|
||||
|
@ -1853,154 +1853,56 @@ void Members::setupList() {
|
|||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
void Members::refreshTilesGeometry() {
|
||||
const auto width = _layout->width();
|
||||
if (_videoTiles.empty()
|
||||
|| !width
|
||||
|| _mode.current() == PanelMode::Wide) {
|
||||
_pinnedVideoWrap->resize(width, 0);
|
||||
return;
|
||||
}
|
||||
auto sizes = base::flat_map<not_null<LargeVideo*>, QSize>();
|
||||
sizes.reserve(_videoTiles.size());
|
||||
for (const auto &tile : _videoTiles) {
|
||||
const auto video = tile.video.get();
|
||||
const auto size = video->trackSize();
|
||||
if (size.isEmpty()) {
|
||||
video->setGeometry(0, 0, width, 0);
|
||||
} else {
|
||||
sizes.emplace(video, size);
|
||||
}
|
||||
}
|
||||
if (sizes.empty()) {
|
||||
_pinnedVideoWrap->resize(width, 0);
|
||||
return;
|
||||
} else if (sizes.size() == 1) {
|
||||
const auto size = sizes.front().second;
|
||||
const auto heightMin = (width * 9) / 16;
|
||||
const auto heightMax = (width * 3) / 4;
|
||||
const auto scaled = size.scaled(
|
||||
QSize(width, heightMax),
|
||||
Qt::KeepAspectRatio);
|
||||
const auto height = std::max(scaled.height(), heightMin);
|
||||
const auto skip = st::groupCallVideoSmallSkip;
|
||||
sizes.front().first->setGeometry(0, 0, width, height);
|
||||
_pinnedVideoWrap->resize(width, height + skip);
|
||||
return;
|
||||
}
|
||||
const auto min = (st::groupCallWidth
|
||||
- st::groupCallMembersMargin.left()
|
||||
- st::groupCallMembersMargin.right()
|
||||
- st::groupCallVideoSmallSkip) / 2;
|
||||
const auto square = (width - st::groupCallVideoSmallSkip) / 2;
|
||||
const auto skip = (width - 2 * square);
|
||||
const auto put = [&](not_null<LargeVideo*> video, int column, int row) {
|
||||
video->setGeometry(
|
||||
(column == 2) ? 0 : column ? (width - square) : 0,
|
||||
row * (min + skip),
|
||||
(column == 2) ? width : square,
|
||||
min);
|
||||
};
|
||||
const auto rows = (sizes.size() + 1) / 2;
|
||||
if (sizes.size() == 3) {
|
||||
put(sizes.front().first, 2, 0);
|
||||
put((sizes.begin() + 1)->first, 0, 1);
|
||||
put((sizes.begin() + 2)->first, 1, 1);
|
||||
} else {
|
||||
auto row = 0;
|
||||
auto column = 0;
|
||||
for (const auto &[video, endpoint] : sizes) {
|
||||
put(video, column, row);
|
||||
if (column) {
|
||||
++row;
|
||||
column = (row + 1 == rows && sizes.size() % 2) ? 2 : 0;
|
||||
} else {
|
||||
column = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
_pinnedVideoWrap->resize(width, rows * (min + skip));
|
||||
void Members::grabViewport() {
|
||||
grabViewport(_mode.current());
|
||||
}
|
||||
|
||||
void Members::setupPinnedVideo() {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto setupTile = [=](
|
||||
const VideoEndpoint &endpoint,
|
||||
const GroupCall::VideoTrack &track) {
|
||||
const auto row = lookupRow(track.peer);
|
||||
Assert(row != nullptr);
|
||||
auto video = std::make_unique<LargeVideo>(
|
||||
_pinnedVideoWrap.get(),
|
||||
st::groupCallLargeVideoNarrow,
|
||||
(_mode.current() == PanelMode::Default),
|
||||
rpl::single(LargeVideoTrack{ track.track.get(), row }),
|
||||
_call->videoEndpointPinnedValue() | rpl::map(_1 == endpoint));
|
||||
|
||||
video->pinToggled(
|
||||
) | rpl::start_with_next([=](bool pinned) {
|
||||
_call->pinVideoEndpoint(pinned ? endpoint : VideoEndpoint{});
|
||||
}, video->lifetime());
|
||||
|
||||
video->requestedQuality(
|
||||
) | rpl::start_with_next([=](VideoQuality quality) {
|
||||
_call->requestVideoQuality(endpoint, quality);
|
||||
}, video->lifetime());
|
||||
|
||||
video->trackSizeValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshTilesGeometry();
|
||||
}, video->lifetime());
|
||||
|
||||
video->clicks(
|
||||
) | rpl::start_to_stream(_enlargeVideoClicks, video->lifetime());
|
||||
|
||||
return VideoTile{
|
||||
.video = std::move(video),
|
||||
.endpoint = endpoint,
|
||||
};
|
||||
};
|
||||
for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
|
||||
_videoTiles.push_back(setupTile(endpoint, track));
|
||||
void Members::grabViewport(PanelMode mode) {
|
||||
if (mode != PanelMode::Default) {
|
||||
_viewportGrabLifetime.destroy();
|
||||
_pinnedVideoWrap->resize(_pinnedVideoWrap->width(), 0);
|
||||
return;
|
||||
}
|
||||
_call->videoStreamActiveUpdates(
|
||||
) | rpl::start_with_next([=](const VideoEndpoint &endpoint) {
|
||||
if (_call->activeVideoTracks().contains(endpoint)) {
|
||||
// Add async (=> the participant row is definitely in Members).
|
||||
crl::on_main(_pinnedVideoWrap, [=] {
|
||||
const auto &tracks = _call->activeVideoTracks();
|
||||
const auto i = tracks.find(endpoint);
|
||||
if (i != end(tracks)) {
|
||||
_videoTiles.push_back(setupTile(endpoint, i->second));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Remove sync.
|
||||
const auto eraseTill = end(_videoTiles);
|
||||
const auto eraseFrom = ranges::remove(
|
||||
_videoTiles,
|
||||
endpoint,
|
||||
&VideoTile::endpoint);
|
||||
if (eraseFrom != eraseTill) {
|
||||
_videoTiles.erase(eraseFrom, eraseTill);
|
||||
refreshTilesGeometry();
|
||||
}
|
||||
_viewport->setMode(mode, _pinnedVideoWrap.get());
|
||||
|
||||
const auto move = [=] {
|
||||
const auto maxTop = _viewport->fullHeight()
|
||||
- _viewport->widget()->height();
|
||||
if (maxTop < 0) {
|
||||
return;
|
||||
}
|
||||
}, _pinnedVideoWrap->lifetime());
|
||||
const auto scrollTop = _scroll->scrollTop();
|
||||
const auto shift = std::min(scrollTop, maxTop);
|
||||
_viewport->setScrollTop(shift);
|
||||
if (_viewport->widget()->y() != shift) {
|
||||
_viewport->widget()->move(0, shift);
|
||||
}
|
||||
};
|
||||
const auto resize = [=] {
|
||||
_viewport->widget()->resize(
|
||||
_layout->width(),
|
||||
std::min(_scroll->height(), _viewport->fullHeight()));
|
||||
};
|
||||
_layout->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
_viewport->resizeToWidth(width);
|
||||
resize();
|
||||
}, _viewportGrabLifetime);
|
||||
|
||||
// New video was pinned or mode changed.
|
||||
rpl::merge(
|
||||
_mode.changes() | rpl::filter(
|
||||
_1 == PanelMode::Default
|
||||
) | rpl::to_empty,
|
||||
_call->videoEndpointPinnedValue() | rpl::filter(_1) | rpl::to_empty
|
||||
) | rpl::start_with_next([=] {
|
||||
_scroll->scrollToY(0);
|
||||
}, _scroll->lifetime());
|
||||
_scroll->heightValue(
|
||||
) | rpl::skip(1) | rpl::start_with_next(resize, _viewportGrabLifetime);
|
||||
|
||||
_layout->widthValue() | rpl::start_with_next([=] {
|
||||
refreshTilesGeometry();
|
||||
}, _pinnedVideoWrap->lifetime());
|
||||
_scroll->scrollTopValue(
|
||||
) | rpl::skip(1) | rpl::start_with_next(move, _viewportGrabLifetime);
|
||||
|
||||
_viewport->fullHeightValue(
|
||||
) | rpl::start_with_next([=](int height) {
|
||||
_pinnedVideoWrap->resize(
|
||||
_pinnedVideoWrap->width(),
|
||||
height);
|
||||
move();
|
||||
resize();
|
||||
}, _viewportGrabLifetime);
|
||||
}
|
||||
|
||||
void Members::resizeEvent(QResizeEvent *e) {
|
||||
|
|
|
@ -26,7 +26,7 @@ class GroupCall;
|
|||
|
||||
namespace Calls::Group {
|
||||
|
||||
class LargeVideo;
|
||||
class Viewport;
|
||||
class MembersRow;
|
||||
struct VolumeRequest;
|
||||
struct MuteRequest;
|
||||
|
@ -38,7 +38,9 @@ class Members final
|
|||
public:
|
||||
Members(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<GroupCall*> call);
|
||||
not_null<GroupCall*> call,
|
||||
not_null<Viewport*> viewport,
|
||||
PanelMode mode);
|
||||
~Members();
|
||||
|
||||
[[nodiscard]] int desiredHeight() const;
|
||||
|
@ -85,19 +87,19 @@ private:
|
|||
void setupAddMember(not_null<GroupCall*> call);
|
||||
void resizeToList();
|
||||
void setupList();
|
||||
void setupPinnedVideo();
|
||||
void setupFakeRoundCorners();
|
||||
|
||||
void grabViewport();
|
||||
void grabViewport(PanelMode mode);
|
||||
void updateControlsGeometry();
|
||||
void refreshTilesGeometry();
|
||||
|
||||
const not_null<GroupCall*> _call;
|
||||
const not_null<Viewport*> _viewport;
|
||||
rpl::variable<PanelMode> _mode = PanelMode();
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
std::unique_ptr<Controller> _listController;
|
||||
not_null<Ui::VerticalLayout*> _layout;
|
||||
const not_null<Ui::RpWidget*> _pinnedVideoWrap;
|
||||
std::vector<VideoTile> _videoTiles;
|
||||
rpl::event_stream<> _enlargeVideoClicks;
|
||||
rpl::variable<Ui::RpWidget*> _addMemberButton = nullptr;
|
||||
ListWidget *_list = nullptr;
|
||||
|
@ -105,7 +107,7 @@ private:
|
|||
|
||||
rpl::variable<bool> _canAddMembers;
|
||||
|
||||
rpl::lifetime _pinnedTrackLifetime;
|
||||
rpl::lifetime _viewportGrabLifetime;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ 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/calls_group_viewport.h"
|
||||
#include "calls/group/ui/desktop_capture_choose_source.h"
|
||||
#include "ui/platform/ui_platform_window_title.h"
|
||||
|
@ -400,6 +399,7 @@ Panel::Panel(not_null<GroupCall*> call)
|
|||
_window->body(),
|
||||
st::groupCallTitle))
|
||||
#endif // !Q_OS_MAC
|
||||
, _viewport(std::make_unique<Viewport>(widget(), _mode.current()))
|
||||
, _videoMode(true) // #TODO calls
|
||||
, _mute(std::make_unique<Ui::CallMuteButton>(
|
||||
widget(),
|
||||
|
@ -946,12 +946,12 @@ void Panel::setupMembers() {
|
|||
_countdown.destroy();
|
||||
_startsWhen.destroy();
|
||||
|
||||
_members.create(widget(), _call);
|
||||
setupPinnedVideo();
|
||||
|
||||
_members->setMode(mode());
|
||||
_members.create(widget(), _call, _viewport.get(), mode());
|
||||
setupVideo();
|
||||
_members->show();
|
||||
|
||||
raiseControls();
|
||||
|
||||
_members->desiredHeightValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateMembersGeometry();
|
||||
|
@ -1087,14 +1087,13 @@ void Panel::raiseControls() {
|
|||
_mute->raise();
|
||||
}
|
||||
|
||||
void Panel::setupPinnedVideo() {
|
||||
using namespace rpl::mappers;
|
||||
_viewport = std::make_unique<Viewport>(widget(), _mode.current());
|
||||
void Panel::setupVideo() {
|
||||
const auto raw = _viewport.get();
|
||||
|
||||
const auto setupTile = [=](
|
||||
const VideoEndpoint &endpoint,
|
||||
const GroupCall::VideoTrack &track) {
|
||||
using namespace rpl::mappers;
|
||||
const auto row = _members->lookupRow(track.peer);
|
||||
Assert(row != nullptr);
|
||||
_viewport->add(
|
||||
|
@ -1144,8 +1143,6 @@ void Panel::setupPinnedVideo() {
|
|||
) | rpl::start_with_next([=](bool inside) {
|
||||
toggleWideControls(inside);
|
||||
}, raw->lifetime());
|
||||
|
||||
raiseControls();
|
||||
}
|
||||
|
||||
void Panel::toggleWideControls(bool shown) {
|
||||
|
@ -1629,7 +1626,6 @@ void Panel::initGeometry() {
|
|||
_window->setGeometry(rect.translated(center - rect.center()));
|
||||
_window->setMinimumSize(rect.size());
|
||||
_window->show();
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
QRect Panel::computeTitleRect() const {
|
||||
|
@ -1652,12 +1648,18 @@ QRect Panel::computeTitleRect() const {
|
|||
}
|
||||
|
||||
bool Panel::updateMode() {
|
||||
if (!_viewport) {
|
||||
return false;
|
||||
}
|
||||
const auto wide = _call->videoCall()
|
||||
&& (widget()->width() >= st::groupCallWideModeWidthMin);
|
||||
const auto mode = wide ? PanelMode::Wide : PanelMode::Default;
|
||||
if (_mode.current() == mode) {
|
||||
return false;
|
||||
}
|
||||
if (!wide && _call->videoEndpointPinned()) {
|
||||
_call->pinVideoEndpoint({});
|
||||
}
|
||||
_mode = mode;
|
||||
if (_title) {
|
||||
_title->setTextColorOverride(wide
|
||||
|
@ -1669,14 +1671,14 @@ bool Panel::updateMode() {
|
|||
} else if (!wide && !_subtitle) {
|
||||
refreshTitle();
|
||||
}
|
||||
_wideControlsShown = _showWideControls = true;
|
||||
_wideControlsAnimation.stop();
|
||||
if (wide) {
|
||||
_viewport->setMode(mode, widget());
|
||||
}
|
||||
if (_members) {
|
||||
_members->setMode(mode);
|
||||
}
|
||||
if (_viewport) {
|
||||
_wideControlsShown = _showWideControls = true;
|
||||
_wideControlsAnimation.stop();
|
||||
_viewport->setMode(mode);
|
||||
}
|
||||
updateButtonsStyles();
|
||||
refreshControlsBackground();
|
||||
updateControlsGeometry();
|
||||
|
@ -2063,11 +2065,12 @@ void Panel::updateMembersGeometry() {
|
|||
top,
|
||||
membersWidth,
|
||||
std::min(desiredHeight, widget()->height() - top - skip));
|
||||
_viewport->widget()->setGeometry(
|
||||
_viewport->setGeometry({
|
||||
skip,
|
||||
top,
|
||||
widget()->width() - membersWidth - 3 * skip,
|
||||
widget()->height() - top - skip);
|
||||
widget()->height() - top - skip,
|
||||
});
|
||||
} else {
|
||||
const auto membersBottom = widget()->height();
|
||||
const auto membersTop = st::groupCallMembersTop;
|
||||
|
|
|
@ -86,7 +86,7 @@ private:
|
|||
void initGeometry();
|
||||
void setupScheduledLabels(rpl::producer<TimeId> date);
|
||||
void setupMembers();
|
||||
void setupPinnedVideo();
|
||||
void setupVideo();
|
||||
void setupJoinAsChangedToasts();
|
||||
void setupTitleChangedToasts();
|
||||
void setupAllowedToSpeakToasts();
|
||||
|
|
|
@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "calls/group/calls_group_viewport.h"
|
||||
|
||||
#include "calls/group/calls_group_large_video.h" // LargeVideoTrack.
|
||||
#include "calls/group/calls_group_viewport_tile.h"
|
||||
#include "calls/group/calls_group_viewport_opengl.h"
|
||||
#include "calls/group/calls_group_viewport_raster.h"
|
||||
|
@ -30,7 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
namespace Calls::Group {
|
||||
|
||||
Viewport::Viewport(QWidget *parent, PanelMode mode)
|
||||
Viewport::Viewport(not_null<QWidget*> parent, PanelMode mode)
|
||||
: _mode(mode)
|
||||
, _content(Ui::GL::CreateSurface(
|
||||
parent,
|
||||
|
@ -64,7 +63,9 @@ void Viewport::setup() {
|
|||
raw->setMouseTracking(true);
|
||||
|
||||
_content->sizeValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
) | rpl::filter([=] {
|
||||
return wide();
|
||||
}) | rpl::start_with_next([=] {
|
||||
updateTilesGeometry();
|
||||
}, lifetime());
|
||||
|
||||
|
@ -92,20 +93,49 @@ void Viewport::setup() {
|
|||
}, lifetime());
|
||||
}
|
||||
|
||||
bool Viewport::wide() const {
|
||||
return (_mode.current() == PanelMode::Wide);
|
||||
void Viewport::setGeometry(QRect geometry) {
|
||||
Expects(wide());
|
||||
|
||||
if (widget()->geometry() != geometry) {
|
||||
_geometryStaleAfterModeChange = false;
|
||||
widget()->setGeometry(geometry);
|
||||
} else if (_geometryStaleAfterModeChange) {
|
||||
_geometryStaleAfterModeChange = false;
|
||||
updateTilesGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
void Viewport::setMode(PanelMode mode) {
|
||||
if (_mode.current() == mode) {
|
||||
void Viewport::resizeToWidth(int width) {
|
||||
Expects(!wide());
|
||||
|
||||
updateTilesGeometry(width);
|
||||
}
|
||||
|
||||
void Viewport::setScrollTop(int scrollTop) {
|
||||
if (_scrollTop == scrollTop) {
|
||||
return;
|
||||
}
|
||||
_scrollTop = scrollTop;
|
||||
updateTilesGeometry();
|
||||
}
|
||||
|
||||
bool Viewport::wide() const {
|
||||
return (_mode == PanelMode::Wide);
|
||||
}
|
||||
|
||||
void Viewport::setMode(PanelMode mode, not_null<QWidget*> parent) {
|
||||
if (_mode == mode && widget()->parent() == parent) {
|
||||
return;
|
||||
}
|
||||
_mode = mode;
|
||||
widget()->setVisible(wide()); // #TODO calls
|
||||
_scrollTop = 0;
|
||||
setControlsShown(1.);
|
||||
updateTilesGeometry();
|
||||
if (_mouseInside.current()) {
|
||||
handleMouseMove(widget()->mapFromGlobal(QCursor::pos()));
|
||||
if (widget()->parent() != parent) {
|
||||
const auto hidden = widget()->isHidden();
|
||||
widget()->setParent(parent);
|
||||
if (!hidden) {
|
||||
widget()->show();
|
||||
}
|
||||
}
|
||||
if (!wide()) {
|
||||
for (const auto &tile : _tiles) {
|
||||
|
@ -143,6 +173,10 @@ void Viewport::handleMouseRelease(QPoint position, Qt::MouseButton button) {
|
|||
}
|
||||
|
||||
void Viewport::handleMouseMove(QPoint position) {
|
||||
updateSelected(position);
|
||||
}
|
||||
|
||||
void Viewport::updateSelected(QPoint position) {
|
||||
if (!widget()->rect().contains(position)) {
|
||||
setSelected({});
|
||||
return;
|
||||
|
@ -164,6 +198,10 @@ void Viewport::handleMouseMove(QPoint position) {
|
|||
setSelected({});
|
||||
}
|
||||
|
||||
void Viewport::updateSelected() {
|
||||
updateSelected(widget()->mapFromGlobal(QCursor::pos()));
|
||||
}
|
||||
|
||||
void Viewport::setControlsShown(float64 shown) {
|
||||
_controlsShownRatio = shown;
|
||||
widget()->update();
|
||||
|
@ -179,11 +217,6 @@ void Viewport::add(
|
|||
std::move(pinned),
|
||||
[=] { widget()->update(); }));
|
||||
|
||||
//video->pinToggled( // #TODO calls
|
||||
//) | rpl::start_with_next([=](bool pinned) {
|
||||
// _call->pinVideoEndpoint(pinned ? endpoint : VideoEndpoint{});
|
||||
//}, video->lifetime());
|
||||
|
||||
_tiles.back()->trackSizeValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateTilesGeometry();
|
||||
|
@ -222,12 +255,36 @@ void Viewport::showLarge(const VideoEndpoint &endpoint) {
|
|||
}
|
||||
|
||||
void Viewport::updateTilesGeometry() {
|
||||
const auto outer = widget()->size();
|
||||
if (_tiles.empty() || outer.isEmpty()) {
|
||||
updateTilesGeometry(widget()->width());
|
||||
}
|
||||
|
||||
void Viewport::updateTilesGeometry(int outerWidth) {
|
||||
const auto mouseInside = _mouseInside.current();
|
||||
const auto guard = gsl::finally([&] {
|
||||
if (mouseInside) {
|
||||
updateSelected();
|
||||
}
|
||||
widget()->update();
|
||||
});
|
||||
|
||||
const auto outerHeight = widget()->height();
|
||||
if (_tiles.empty() || !outerWidth) {
|
||||
_fullHeight = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto guard = gsl::finally([&] { widget()->update(); });
|
||||
if (wide()) {
|
||||
updateTilesGeometryWide(outerWidth, outerHeight);
|
||||
_fullHeight = 0;
|
||||
} else {
|
||||
updateTilesGeometryNarrow(outerWidth);
|
||||
}
|
||||
}
|
||||
|
||||
void Viewport::updateTilesGeometryWide(int outerWidth, int outerHeight) {
|
||||
if (!outerHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct Geometry {
|
||||
QSize size;
|
||||
|
@ -242,7 +299,7 @@ void Viewport::updateTilesGeometry() {
|
|||
? QSize()
|
||||
: video->trackSize();
|
||||
if (size.isEmpty()) {
|
||||
setTileGeometry(video, { 0, 0, outer.width(), 0 });
|
||||
setTileGeometry(video, { 0, 0, outerWidth, 0 });
|
||||
} else {
|
||||
sizes.emplace(video, Geometry{ size });
|
||||
}
|
||||
|
@ -250,7 +307,7 @@ void Viewport::updateTilesGeometry() {
|
|||
if (sizes.size() == 1) {
|
||||
setTileGeometry(
|
||||
sizes.front().first,
|
||||
{ 0, 0, outer.width(), outer.height() });
|
||||
{ 0, 0, outerWidth, outerHeight });
|
||||
return;
|
||||
}
|
||||
if (sizes.empty()) {
|
||||
|
@ -265,14 +322,14 @@ void Viewport::updateTilesGeometry() {
|
|||
{
|
||||
auto index = 0;
|
||||
const auto columns = slices;
|
||||
const auto sizew = (outer.width() + skip) / float64(columns);
|
||||
const auto sizew = (outerWidth + skip) / float64(columns);
|
||||
for (auto column = 0; column != columns; ++column) {
|
||||
const auto left = int(std::round(column * sizew));
|
||||
const auto width = int(std::round(column * sizew + sizew - skip))
|
||||
- left;
|
||||
const auto rows = int(std::round((count - index)
|
||||
/ float64(columns - column)));
|
||||
const auto sizeh = (outer.height() + skip) / float64(rows);
|
||||
const auto sizeh = (outerHeight + skip) / float64(rows);
|
||||
for (auto row = 0; row != rows; ++row) {
|
||||
const auto top = int(std::round(row * sizeh));
|
||||
const auto height = int(std::round(
|
||||
|
@ -297,14 +354,14 @@ void Viewport::updateTilesGeometry() {
|
|||
{
|
||||
auto index = 0;
|
||||
const auto rows = slices;
|
||||
const auto sizeh = (outer.height() + skip) / float64(rows);
|
||||
const auto sizeh = (outerHeight + skip) / float64(rows);
|
||||
for (auto row = 0; row != rows; ++row) {
|
||||
const auto top = int(std::round(row * sizeh));
|
||||
const auto height = int(std::round(row * sizeh + sizeh - skip))
|
||||
- top;
|
||||
const auto columns = int(std::round((count - index)
|
||||
/ float64(rows - row)));
|
||||
const auto sizew = (outer.width() + skip) / float64(columns);
|
||||
const auto sizew = (outerWidth + skip) / float64(columns);
|
||||
for (auto column = 0; column != columns; ++column) {
|
||||
const auto left = int(std::round(column * sizew));
|
||||
const auto width = int(std::round(
|
||||
|
@ -334,6 +391,72 @@ void Viewport::updateTilesGeometry() {
|
|||
}
|
||||
}
|
||||
|
||||
void Viewport::updateTilesGeometryNarrow(int outerWidth) {
|
||||
const auto y = -_scrollTop;
|
||||
|
||||
auto sizes = base::flat_map<not_null<VideoTile*>, QSize>();
|
||||
sizes.reserve(_tiles.size());
|
||||
for (const auto &tile : _tiles) {
|
||||
const auto video = tile.get();
|
||||
const auto size = video->trackSize();
|
||||
if (size.isEmpty()) {
|
||||
video->setGeometry({ 0, y, outerWidth, 0 });
|
||||
} else {
|
||||
sizes.emplace(video, size);
|
||||
}
|
||||
}
|
||||
if (sizes.empty()) {
|
||||
_fullHeight = 0;
|
||||
return;
|
||||
} else if (sizes.size() == 1) {
|
||||
const auto size = sizes.front().second;
|
||||
const auto heightMin = (outerWidth * 9) / 16;
|
||||
const auto heightMax = (outerWidth * 3) / 4;
|
||||
const auto scaled = size.scaled(
|
||||
QSize(outerWidth, heightMax),
|
||||
Qt::KeepAspectRatio);
|
||||
const auto height = std::max(scaled.height(), heightMin);
|
||||
const auto skip = st::groupCallVideoSmallSkip;
|
||||
sizes.front().first->setGeometry({ 0, y, outerWidth, height });
|
||||
_fullHeight = height + skip;
|
||||
return;
|
||||
}
|
||||
const auto min = (st::groupCallWidth
|
||||
- st::groupCallMembersMargin.left()
|
||||
- st::groupCallMembersMargin.right()
|
||||
- st::groupCallVideoSmallSkip) / 2;
|
||||
const auto square = (outerWidth - st::groupCallVideoSmallSkip) / 2;
|
||||
const auto skip = (outerWidth - 2 * square);
|
||||
const auto put = [&](not_null<VideoTile*> tile, int column, int row) {
|
||||
tile->setGeometry({
|
||||
(column == 2) ? 0 : column ? (outerWidth - square) : 0,
|
||||
y + row * (min + skip),
|
||||
(column == 2) ? outerWidth : square,
|
||||
min,
|
||||
});
|
||||
};
|
||||
const auto rows = (sizes.size() + 1) / 2;
|
||||
if (sizes.size() == 3) {
|
||||
put(sizes.front().first, 2, 0);
|
||||
put((sizes.begin() + 1)->first, 0, 1);
|
||||
put((sizes.begin() + 2)->first, 1, 1);
|
||||
} else {
|
||||
auto row = 0;
|
||||
auto column = 0;
|
||||
for (const auto &[video, endpoint] : sizes) {
|
||||
put(video, column, row);
|
||||
if (column) {
|
||||
++row;
|
||||
column = (row + 1 == rows && sizes.size() % 2) ? 2 : 0;
|
||||
} else {
|
||||
column = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
_fullHeight = rows * (min + skip);
|
||||
|
||||
}
|
||||
|
||||
void Viewport::setTileGeometry(not_null<VideoTile*> tile, QRect geometry) {
|
||||
tile->setGeometry(geometry);
|
||||
|
||||
|
@ -400,23 +523,31 @@ Ui::GL::ChosenRenderer Viewport::chooseRenderer(
|
|||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<VideoPinToggle> Viewport::pinToggled() const {
|
||||
int Viewport::fullHeight() const {
|
||||
return _fullHeight.current();
|
||||
}
|
||||
|
||||
rpl::producer<int> Viewport::fullHeightValue() const {
|
||||
return _fullHeight.value();
|
||||
}
|
||||
|
||||
rpl::producer<VideoPinToggle> Viewport::pinToggled() const {
|
||||
return _pinToggles.events();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<VideoEndpoint> Viewport::clicks() const {
|
||||
rpl::producer<VideoEndpoint> Viewport::clicks() const {
|
||||
return _clicks.events();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<VideoQualityRequest> Viewport::qualityRequests() const {
|
||||
rpl::producer<VideoQualityRequest> Viewport::qualityRequests() const {
|
||||
return _qualityRequests.events();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> Viewport::mouseInsideValue() const {
|
||||
rpl::producer<bool> Viewport::mouseInsideValue() const {
|
||||
return _mouseInside.value();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::lifetime &Viewport::lifetime() {
|
||||
rpl::lifetime &Viewport::lifetime() {
|
||||
return _content->lifetime();
|
||||
}
|
||||
|
||||
|
|
|
@ -37,18 +37,41 @@ namespace Calls::Group {
|
|||
class MembersRow;
|
||||
enum class PanelMode;
|
||||
enum class VideoQuality;
|
||||
struct LargeVideoTrack;
|
||||
|
||||
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 Viewport final {
|
||||
public:
|
||||
Viewport(QWidget *parent, PanelMode mode);
|
||||
Viewport(not_null<QWidget*> parent, PanelMode mode);
|
||||
~Viewport();
|
||||
|
||||
[[nodiscard]] not_null<QWidget*> widget() const;
|
||||
[[nodiscard]] not_null<Ui::RpWidgetWrap*> rp() const;
|
||||
|
||||
void setMode(PanelMode mode);
|
||||
void setMode(PanelMode mode, not_null<QWidget*> parent);
|
||||
void setControlsShown(float64 shown);
|
||||
void setGeometry(QRect geometry);
|
||||
void resizeToWidth(int newWidth);
|
||||
void setScrollTop(int scrollTop);
|
||||
|
||||
void add(
|
||||
const VideoEndpoint &endpoint,
|
||||
|
@ -57,6 +80,8 @@ public:
|
|||
void remove(const VideoEndpoint &endpoint);
|
||||
void showLarge(const VideoEndpoint &endpoint);
|
||||
|
||||
[[nodiscard]] int fullHeight() const;
|
||||
[[nodiscard]] rpl::producer<int> fullHeightValue() const;
|
||||
[[nodiscard]] rpl::producer<VideoPinToggle> pinToggled() const;
|
||||
[[nodiscard]] rpl::producer<VideoEndpoint> clicks() const;
|
||||
[[nodiscard]] rpl::producer<VideoQualityRequest> qualityRequests() const;
|
||||
|
@ -88,6 +113,9 @@ private:
|
|||
[[nodiscard]] bool wide() const;
|
||||
|
||||
void updateTilesGeometry();
|
||||
void updateTilesGeometry(int outerWidth);
|
||||
void updateTilesGeometryWide(int outerWidth, int outerHeight);
|
||||
void updateTilesGeometryNarrow(int outerWidth);
|
||||
void setTileGeometry(not_null<VideoTile*> tile, QRect geometry);
|
||||
|
||||
void setSelected(Selection value);
|
||||
|
@ -96,14 +124,19 @@ private:
|
|||
void handleMousePress(QPoint position, Qt::MouseButton button);
|
||||
void handleMouseRelease(QPoint position, Qt::MouseButton button);
|
||||
void handleMouseMove(QPoint position);
|
||||
void updateSelected(QPoint position);
|
||||
void updateSelected();
|
||||
|
||||
[[nodiscard]] Ui::GL::ChosenRenderer chooseRenderer(
|
||||
Ui::GL::Capabilities capabilities);
|
||||
|
||||
Fn<void(const Textures &)> _freeTextures;
|
||||
rpl::variable<PanelMode> _mode;
|
||||
PanelMode _mode = PanelMode();
|
||||
bool _geometryStaleAfterModeChange = false;
|
||||
const std::unique_ptr<Ui::RpWidgetWrap> _content;
|
||||
std::vector<std::unique_ptr<VideoTile>> _tiles;
|
||||
rpl::variable<int> _fullHeight = 0;
|
||||
int _scrollTop = 0;
|
||||
QImage _shadow;
|
||||
rpl::event_stream<VideoEndpoint> _clicks;
|
||||
rpl::event_stream<VideoPinToggle> _pinToggles;
|
||||
|
|
|
@ -313,7 +313,7 @@ void Viewport::RendererGL::fillBackground(not_null<QOpenGLFunctions*> f) {
|
|||
const auto radiuses = QMargins{ radius, radius, radius, radius };
|
||||
auto bg = QRegion(QRect(QPoint(), _viewport));
|
||||
for (const auto &tile : _owner->_tiles) {
|
||||
bg -= tile->geometry().marginsRemoved(radiuses);
|
||||
bg -= tileGeometry(tile.get()).marginsRemoved(radiuses);
|
||||
}
|
||||
if (bg.isEmpty()) {
|
||||
return;
|
||||
|
@ -343,15 +343,18 @@ void Viewport::RendererGL::paintTile(
|
|||
return;
|
||||
}
|
||||
|
||||
const auto geometry = tile->geometry();
|
||||
const auto geometry = tileGeometry(tile);
|
||||
const auto x = geometry.x();
|
||||
const auto y = geometry.y();
|
||||
const auto width = geometry.width();
|
||||
const auto height = geometry.height();
|
||||
const auto expand = !_owner->wide()/* && !tile->screencast()*/;
|
||||
const auto scaled = Media::View::FlipSizeByRotation(
|
||||
image.size(),
|
||||
data.rotation
|
||||
).scaled(QSize(width, height), Qt::KeepAspectRatio);
|
||||
).scaled(
|
||||
QSize(width, height),
|
||||
(expand ? Qt::KeepAspectRatioByExpanding : Qt::KeepAspectRatio));
|
||||
if (scaled.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -452,6 +455,16 @@ void Viewport::RendererGL::paintTile(
|
|||
f->glDisableVertexAttribArray(position);
|
||||
}
|
||||
|
||||
QRect Viewport::RendererGL::tileGeometry(not_null<VideoTile*> tile) const {
|
||||
const auto raster = tile->geometry();
|
||||
return {
|
||||
raster.x(),
|
||||
_viewport.height() - raster.y() - raster.height(),
|
||||
raster.width(),
|
||||
raster.height(),
|
||||
};
|
||||
}
|
||||
|
||||
void Viewport::RendererGL::freeTextures(not_null<QOpenGLFunctions*> f) {
|
||||
for (const auto &textures : base::take(_texturesToFree)) {
|
||||
f->glDeleteTextures(textures.values.size(), textures.values.data());
|
||||
|
|
|
@ -45,6 +45,7 @@ private:
|
|||
not_null<QOpenGLFunctions*> f,
|
||||
not_null<VideoTile*> tile);
|
||||
void freeTextures(not_null<QOpenGLFunctions*> f);
|
||||
[[nodiscard]] QRect tileGeometry(not_null<VideoTile*> tile) const;
|
||||
|
||||
const not_null<Viewport*> _owner;
|
||||
|
||||
|
|
|
@ -9,8 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "calls/group/calls_group_viewport.h"
|
||||
#include "calls/group/calls_group_call.h"
|
||||
#include "calls/group/calls_group_large_video.h" // LargeVideoTrack.
|
||||
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/cross_line.h"
|
||||
#include "ui/round_rect.h"
|
||||
|
|
Loading…
Add table
Reference in a new issue