Support mode switching in single widget Viewport.

This commit is contained in:
John Preston 2021-05-24 13:06:34 +04:00
parent 3edb2d08ba
commit b864563f47
12 changed files with 303 additions and 1244 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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) {

View file

@ -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;
};

View file

@ -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;

View file

@ -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();

View file

@ -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();
}

View file

@ -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;

View file

@ -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());

View file

@ -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;

View file

@ -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"