From b864563f47c51a1c1b243ef84c2ebf9f0dc4d787 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 24 May 2021 13:06:34 +0400 Subject: [PATCH] Support mode switching in single widget Viewport. --- Telegram/CMakeLists.txt | 2 - .../calls/group/calls_group_large_video.cpp | 907 ------------------ .../calls/group/calls_group_large_video.h | 115 --- .../calls/group/calls_group_members.cpp | 214 ++--- .../calls/group/calls_group_members.h | 14 +- .../calls/group/calls_group_panel.cpp | 39 +- .../calls/group/calls_group_panel.h | 2 +- .../calls/group/calls_group_viewport.cpp | 191 +++- .../calls/group/calls_group_viewport.h | 41 +- .../group/calls_group_viewport_opengl.cpp | 19 +- .../calls/group/calls_group_viewport_opengl.h | 1 + .../calls/group/calls_group_viewport_tile.h | 2 - 12 files changed, 303 insertions(+), 1244 deletions(-) delete mode 100644 Telegram/SourceFiles/calls/group/calls_group_large_video.cpp delete mode 100644 Telegram/SourceFiles/calls/group/calls_group_large_video.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index da57f0d433..d2f259f7f5 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -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 diff --git a/Telegram/SourceFiles/calls/group/calls_group_large_video.cpp b/Telegram/SourceFiles/calls/group/calls_group_large_video.cpp deleted file mode 100644 index f79425f175..0000000000 --- a/Telegram/SourceFiles/calls/group/calls_group_large_video.cpp +++ /dev/null @@ -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 -#include -#include -#include - -namespace Calls::Group { -namespace { - -constexpr auto kShadowMaxAlpha = 80; - -struct ShaderPart { - QString header; - QString body; -}; - -[[nodiscard]] QString VertexShader(const std::vector &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 &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 MakeShader( - not_null 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 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 f, - not_null buffer, - not_null program, - QSize viewport, - const QColor &color, - Fn additional = nullptr); - -private: - static constexpr auto kMaxTriangles = 8; - std::array 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 f, - not_null buffer, - not_null program, - QSize viewport, - const QColor &color, - Fn 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 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 owner) : _owner(owner) { - } - ~RendererGL(); - - void init( - not_null widget, - not_null f) override; - - void deinit( - not_null widget, - not_null f) override; - - void resize( - not_null widget, - not_null f, - int w, - int h) override; - - void paint( - not_null widget, - not_null f) override; - -private: - const not_null _owner; - - std::array _textures = {}; - std::optional _frameBuffer; - std::optional _fillBuffer; - std::optional _cornersBgBuffer; - std::optional _frameProgram; - std::optional _fillProgram; - std::optional _cornersBgProgram; - qint64 _frameKey = 0; - -}; - -LargeVideo::PinButton::PinButton( - not_null 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 widget, - not_null 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 widget, - not_null 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 widget, - not_null f, - int w, - int h) { - f->glViewport(0, 0, w, h); -} - -void LargeVideo::RendererGL::paint( - not_null widget, - not_null 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, 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 track, - rpl::producer pinned) -: _content(Ui::GL::CreateSurface( - parent, - [=](Ui::GL::Capabilities capabilities) { - return chooseRenderer(capabilities); - })) -, _st(st) -, _pinButton((_st.pinPosition.x() >= 0) - ? std::make_unique(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 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 _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(this), - .backend = Ui::GL::Backend::OpenGL, - }; - } - return { - .renderer = std::make_unique(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 LargeVideo::pinToggled() const { - return _pinButton - ? _pinButton->area.clicks() | rpl::map([=] { return !_pinned; }) - : rpl::never() | rpl::type_erased(); -} - -QSize LargeVideo::trackSize() const { - return _trackSize.current(); -} - -rpl::producer LargeVideo::trackSizeValue() const { - return _trackSize.value(); -} - -rpl::producer 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 LargeVideo::widget() const { - return _content->rpWidget(); -} - -void LargeVideo::setup( - rpl::producer track, - rpl::producer pinned) { - widget()->setAttribute(Qt::WA_OpaquePaintEvent); - - _content->events( - ) | rpl::start_with_next([=](not_null 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( - e.get())->button() == Qt::LeftButton) { - _mouseDown = true; - } else if (type == QEvent::MouseButtonRelease - && static_cast( - 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 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 diff --git a/Telegram/SourceFiles/calls/group/calls_group_large_video.h b/Telegram/SourceFiles/calls/group/calls_group_large_video.h deleted file mode 100644 index 4d854ebe9e..0000000000 --- a/Telegram/SourceFiles/calls/group/calls_group_large_video.h +++ /dev/null @@ -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 track, - rpl::producer 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 pinToggled() const; - [[nodiscard]] rpl::producer<> clicks() const { - return _clicks.events(); - } - - [[nodiscard]] QSize trackSize() const; - [[nodiscard]] rpl::producer trackSizeValue() const; - [[nodiscard]] rpl::producer requestedQuality() const; - - [[nodiscard]] rpl::lifetime &lifetime(); - -private: - struct PinButton; - class RendererGL; - - [[nodiscard]] not_null widget() const; - - void setup( - rpl::producer track, - rpl::producer pinned); - void setupControls(rpl::producer 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 _content; - const style::GroupCallLargeVideo &_st; - LargeVideoTrack _track; - QImage _shadow; - std::unique_ptr _pinButton; - rpl::event_stream<> _clicks; - const bool _smallLayout = true; - bool _pinned = false; - bool _mouseDown = false; - float64 _controlsShownRatio = 1.; - rpl::variable _trackSize; - rpl::variable _requestedQuality; - rpl::lifetime _trackLifetime; - -}; - -} // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index b14c5e6303..646b60caac 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -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 video; - VideoEndpoint endpoint; -}; - class Members::Controller final : public PeerListController , public MembersRowDelegate @@ -58,7 +53,8 @@ class Members::Controller final public: Controller( not_null call, - not_null menuParent); + not_null menuParent, + PanelMode mode); ~Controller(); using MuteRequest = Group::MuteRequest; @@ -226,11 +222,13 @@ private: Members::Controller::Controller( not_null call, - not_null menuParent) + not_null 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 Members::Controller::createInvitedRow( Members::Members( not_null parent, - not_null call) + not_null call, + not_null viewport, + PanelMode mode) : RpWidget(parent) , _call(call) +, _viewport(viewport) +, _mode(mode) , _scroll(this) -, _listController(std::make_unique(call, parent)) +, _listController(std::make_unique(call, parent, mode)) , _layout(_scroll->setOwnedWidget( object_ptr(_scroll.data()))) , _pinnedVideoWrap(_layout->add(object_ptr(_layout.get()))) { @@ -1656,7 +1658,7 @@ Members::Members( setContent(_list); setupFakeRoundCorners(); _listController->setDelegate(static_cast(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, 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 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( - _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) { diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.h b/Telegram/SourceFiles/calls/group/calls_group_members.h index 167a87856c..107bf99dbe 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members.h @@ -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 parent, - not_null call); + not_null call, + not_null viewport, + PanelMode mode); ~Members(); [[nodiscard]] int desiredHeight() const; @@ -85,19 +87,19 @@ private: void setupAddMember(not_null call); void resizeToList(); void setupList(); - void setupPinnedVideo(); void setupFakeRoundCorners(); + void grabViewport(); + void grabViewport(PanelMode mode); void updateControlsGeometry(); - void refreshTilesGeometry(); const not_null _call; + const not_null _viewport; rpl::variable _mode = PanelMode(); object_ptr _scroll; std::unique_ptr _listController; not_null _layout; const not_null _pinnedVideoWrap; - std::vector _videoTiles; rpl::event_stream<> _enlargeVideoClicks; rpl::variable _addMemberButton = nullptr; ListWidget *_list = nullptr; @@ -105,7 +107,7 @@ private: rpl::variable _canAddMembers; - rpl::lifetime _pinnedTrackLifetime; + rpl::lifetime _viewportGrabLifetime; }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 84886a161d..e32c981857 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -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 call) _window->body(), st::groupCallTitle)) #endif // !Q_OS_MAC +, _viewport(std::make_unique(widget(), _mode.current())) , _videoMode(true) // #TODO calls , _mute(std::make_unique( 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(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; diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 26b6292cd1..33cb7cbc6a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -86,7 +86,7 @@ private: void initGeometry(); void setupScheduledLabels(rpl::producer date); void setupMembers(); - void setupPinnedVideo(); + void setupVideo(); void setupJoinAsChangedToasts(); void setupTitleChangedToasts(); void setupAllowedToSpeakToasts(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp index b6ffd06543..c6940e676b 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp @@ -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 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 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, 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 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 tile, QRect geometry) { tile->setGeometry(geometry); @@ -400,23 +523,31 @@ Ui::GL::ChosenRenderer Viewport::chooseRenderer( }; } -[[nodiscard]] rpl::producer Viewport::pinToggled() const { +int Viewport::fullHeight() const { + return _fullHeight.current(); +} + +rpl::producer Viewport::fullHeightValue() const { + return _fullHeight.value(); +} + +rpl::producer Viewport::pinToggled() const { return _pinToggles.events(); } -[[nodiscard]] rpl::producer Viewport::clicks() const { +rpl::producer Viewport::clicks() const { return _clicks.events(); } -[[nodiscard]] rpl::producer Viewport::qualityRequests() const { +rpl::producer Viewport::qualityRequests() const { return _qualityRequests.events(); } -[[nodiscard]] rpl::producer Viewport::mouseInsideValue() const { +rpl::producer Viewport::mouseInsideValue() const { return _mouseInside.value(); } -[[nodiscard]] rpl::lifetime &Viewport::lifetime() { +rpl::lifetime &Viewport::lifetime() { return _content->lifetime(); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport.h b/Telegram/SourceFiles/calls/group/calls_group_viewport.h index dc2ecb6cab..cb1a2d3109 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport.h +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport.h @@ -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 parent, PanelMode mode); ~Viewport(); [[nodiscard]] not_null widget() const; [[nodiscard]] not_null rp() const; - void setMode(PanelMode mode); + void setMode(PanelMode mode, not_null 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 fullHeightValue() const; [[nodiscard]] rpl::producer pinToggled() const; [[nodiscard]] rpl::producer clicks() const; [[nodiscard]] rpl::producer 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 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 _freeTextures; - rpl::variable _mode; + PanelMode _mode = PanelMode(); + bool _geometryStaleAfterModeChange = false; const std::unique_ptr _content; std::vector> _tiles; + rpl::variable _fullHeight = 0; + int _scrollTop = 0; QImage _shadow; rpl::event_stream _clicks; rpl::event_stream _pinToggles; diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp index ce448f456b..e56639197c 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp @@ -313,7 +313,7 @@ void Viewport::RendererGL::fillBackground(not_null 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 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 f) { for (const auto &textures : base::take(_texturesToFree)) { f->glDeleteTextures(textures.values.size(), textures.values.data()); diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.h b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.h index 1600efffee..93ef2c457f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.h +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.h @@ -45,6 +45,7 @@ private: not_null f, not_null tile); void freeTextures(not_null f); + [[nodiscard]] QRect tileGeometry(not_null tile) const; const not_null _owner; diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h index 7d75879a69..427350730d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h @@ -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"