diff --git a/Telegram/Resources/icons/calls/video_back.png b/Telegram/Resources/icons/calls/video_back.png new file mode 100644 index 0000000000..77da5ba037 Binary files /dev/null and b/Telegram/Resources/icons/calls/video_back.png differ diff --git a/Telegram/Resources/icons/calls/video_back@2x.png b/Telegram/Resources/icons/calls/video_back@2x.png new file mode 100644 index 0000000000..be90e58104 Binary files /dev/null and b/Telegram/Resources/icons/calls/video_back@2x.png differ diff --git a/Telegram/Resources/icons/calls/video_back@3x.png b/Telegram/Resources/icons/calls/video_back@3x.png new file mode 100644 index 0000000000..9013645fcd Binary files /dev/null and b/Telegram/Resources/icons/calls/video_back@3x.png differ diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 239abc4169..f8fb92e21c 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1202,6 +1202,7 @@ GroupCallVideoTile { pinPosition: point; pinPadding: margins; pinTextPosition: point; + back: icon; iconPosition: point; } @@ -1219,6 +1220,7 @@ groupCallVideoTile: GroupCallVideoTile { pinPosition: point(18px, 18px); pinPadding: margins(6px, 2px, 12px, 1px); pinTextPosition: point(1px, 3px); + back: icon {{ "calls/video_back", groupCallVideoTextFg }}; iconPosition: point(10px, 5px); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index dfd384dec4..b6740f7e49 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -1187,8 +1187,19 @@ base::unique_qptr Members::Controller::createRowContextMenu( }); if (const auto real = _call->lookupReal()) { + auto oneFound = false; + auto hasTwoOrMore = false; + for (const auto &[endpoint, track] : _call->activeVideoTracks()) { + if (_call->shownVideoTracks().contains(endpoint)) { + if (oneFound) { + hasTwoOrMore = true; + break; + } + oneFound = true; + } + } const auto participant = real->participantByPeer(participantPeer); - if (participant) { + if (participant && hasTwoOrMore) { const auto &large = _call->videoEndpointLarge(); const auto pinned = _call->videoEndpointPinned(); const auto &camera = computeCameraEndpoint(participant); diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp index c0123dcd39..1fea883da4 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport.cpp @@ -134,10 +134,10 @@ void Viewport::setMode(PanelMode mode, not_null parent) { } if (!wide()) { for (const auto &tile : _tiles) { - tile->togglePinShown(false); + tile->toggleTopControlsShown(false); } } else if (_selected.tile) { - _selected.tile->togglePinShown(true); + _selected.tile->toggleTopControlsShown(true); } } @@ -154,7 +154,9 @@ void Viewport::handleMouseRelease(QPoint position, Qt::MouseButton button) { if (pressed == _selected) { if (button == Qt::RightButton) { tile->row()->showContextMenu(); - } else if (!wide()) { + } else if (!wide() + || (_hasTwoOrMore && !_large) + || pressed.element == Selection::Element::BackButton) { _clicks.fire_copy(tile->endpoint()); } else if (pressed.element == Selection::Element::PinButton) { _pinToggles.fire(!tile->pinned()); @@ -177,10 +179,14 @@ void Viewport::updateSelected(QPoint position) { if (geometry.contains(position)) { const auto pin = wide() && tile->pinOuter().contains(position - geometry.topLeft()); + const auto back = wide() + && tile->backOuter().contains(position - geometry.topLeft()); setSelected({ .tile = tile.get(), .element = (pin ? Selection::Element::PinButton + : back + ? Selection::Element::BackButton : Selection::Element::Tile), }); return; @@ -238,6 +244,7 @@ void Viewport::showLarge(const VideoEndpoint &endpoint) { const auto large = (i != end(_tiles)) ? i->get() : nullptr; if (_large != large) { _large = large; + updateTopControlsVisibility(); updateTilesGeometry(); } } @@ -263,12 +270,40 @@ void Viewport::updateTilesGeometry(int outerWidth) { if (wide()) { updateTilesGeometryWide(outerWidth, outerHeight); + refreshHasTwoOrMore(); _fullHeight = 0; } else { updateTilesGeometryNarrow(outerWidth); } } +void Viewport::refreshHasTwoOrMore() { + auto hasTwoOrMore = false; + auto oneFound = false; + for (const auto &tile : _tiles) { + if (!tile->trackSize().isEmpty()) { + if (oneFound) { + hasTwoOrMore = true; + break; + } + oneFound = true; + } + } + if (_hasTwoOrMore == hasTwoOrMore) { + return; + } + _hasTwoOrMore = hasTwoOrMore; + updateCursor(); + updateTopControlsVisibility(); +} + +void Viewport::updateTopControlsVisibility() { + if (_selected.tile) { + _selected.tile->toggleTopControlsShown( + _hasTwoOrMore && wide() && _large && _large == _selected.tile); + } +} + void Viewport::updateTilesGeometryWide(int outerWidth, int outerHeight) { if (!outerHeight) { return; @@ -485,14 +520,19 @@ void Viewport::setSelected(Selection value) { return; } if (_selected.tile) { - _selected.tile->togglePinShown(false); + _selected.tile->toggleTopControlsShown(false); } _selected = value; - if (_selected.tile && wide()) { - _selected.tile->togglePinShown(true); - } + updateTopControlsVisibility(); + updateCursor(); +} + +void Viewport::updateCursor() { const auto pointer = _selected.tile - && (!wide() || _selected.element == Selection::Element::PinButton); + && (!wide() + || (_hasTwoOrMore && !_large) + || _selected.element == Selection::Element::PinButton + || _selected.element == Selection::Element::BackButton); widget()->setCursor(pointer ? style::cur_pointer : style::cur_default); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport.h b/Telegram/SourceFiles/calls/group/calls_group_viewport.h index bcd9a8dad5..6361bf0137 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport.h +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport.h @@ -97,6 +97,7 @@ private: None, Tile, PinButton, + BackButton, }; VideoTile *tile = nullptr; Element element = Element::None; @@ -111,11 +112,14 @@ private: void setup(); [[nodiscard]] bool wide() const; + void updateCursor(); void updateTilesGeometry(); void updateTilesGeometry(int outerWidth); void updateTilesGeometryWide(int outerWidth, int outerHeight); void updateTilesGeometryNarrow(int outerWidth); void setTileGeometry(not_null tile, QRect geometry); + void refreshHasTwoOrMore(); + void updateTopControlsVisibility(); void setSelected(Selection value); void setPressed(Selection value); @@ -135,6 +139,7 @@ private: const std::unique_ptr _content; std::vector> _tiles; rpl::variable _fullHeight = 0; + bool _hasTwoOrMore = false; int _scrollTop = 0; QImage _shadow; rpl::event_stream _clicks; diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp index 55ee974a48..495833a08b 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp @@ -262,7 +262,7 @@ void Viewport::RendererGL::init( _frameBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw); _frameBuffer->create(); _frameBuffer->bind(); - constexpr auto kQuads = 6; + constexpr auto kQuads = 7; constexpr auto kQuadVertices = kQuads * 4; constexpr auto kQuadValues = kQuadVertices * 4; constexpr auto kValues = kQuadValues + 8; // Blur texture coordinates. @@ -486,6 +486,13 @@ void Viewport::RendererGL::paintTile( geometry); const auto pinRect = transformRect(pin.geometry); + // Back. + const auto back = _buttons.texturedRect( + tile->backInner().translated(x, y), + _back, + geometry); + const auto backRect = transformRect(back.geometry); + // Mute. const auto &icon = st::groupCallVideoCrossLine.icon; const auto iconLeft = x + width - st.iconPosition.x() - icon.width(); @@ -573,6 +580,19 @@ void Viewport::RendererGL::paintTile( pinRect.left(), pinRect.bottom(), pin.texture.left(), pin.texture.top(), + // Back button. + backRect.left(), backRect.top(), + back.texture.left(), back.texture.bottom(), + + backRect.right(), backRect.top(), + back.texture.right(), back.texture.bottom(), + + backRect.right(), backRect.bottom(), + back.texture.right(), back.texture.top(), + + backRect.left(), backRect.bottom(), + back.texture.left(), back.texture.top(), + // Mute icon. muteRect.left(), muteRect.top(), mute.texture.left(), mute.texture.bottom(), @@ -683,6 +703,7 @@ void Viewport::RendererGL::paintTile( if (pinVisible) { FillTexturedRectangle(f, &*_imageProgram, 14); + FillTexturedRectangle(f, &*_imageProgram, 18); } if (nameShift == fullNameShift) { @@ -691,13 +712,13 @@ void Viewport::RendererGL::paintTile( // Mute. if (!muteRect.empty()) { - FillTexturedRectangle(f, &*_imageProgram, 18); + FillTexturedRectangle(f, &*_imageProgram, 22); } // Name. if (!nameRect.empty()) { _names.bind(f); - FillTexturedRectangle(f, &*_imageProgram, 22); + FillTexturedRectangle(f, &*_imageProgram, 26); } } @@ -905,15 +926,20 @@ void Viewport::RendererGL::ensureButtonsImage() { const auto factor = cIntRetinaFactor(); const auto pinOnSize = VideoTile::PinInnerSize(true); const auto pinOffSize = VideoTile::PinInnerSize(false); + const auto backSize = VideoTile::BackInnerSize(); const auto muteSize = st::groupCallVideoCrossLine.icon.size(); const auto fullSize = QSize( std::max({ pinOnSize.width(), pinOffSize.width(), + backSize.width(), 2 * muteSize.width(), }), - pinOnSize.height() + pinOffSize.height() + muteSize.height()); + (pinOnSize.height() + + pinOffSize.height() + + backSize.height() + + muteSize.height())); const auto imageSize = fullSize * factor; auto image = _buttons.takeImage(); if (image.size() != imageSize) { @@ -947,7 +973,19 @@ void Viewport::RendererGL::ensureButtonsImage() { &_pinBackground, &_pinIcon); - const auto muteTop = pinOnSize.height() + pinOffSize.height(); + _back = QRect( + QPoint(0, pinOnSize.height() + pinOffSize.height()) * factor, + backSize * factor); + VideoTile::PaintBackButton( + p, + 0, + pinOnSize.height() + pinOffSize.height(), + fullSize.width(), + &_pinBackground); + + const auto muteTop = pinOnSize.height() + + pinOffSize.height() + + backSize.height(); _muteOn = QRect(QPoint(0, muteTop) * factor, muteSize * factor); _muteIcon.paint(p, { 0, muteTop }, 1.); diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.h b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.h index 5e65f17640..3a27b03989 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.h +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.h @@ -120,6 +120,7 @@ private: Ui::GL::Image _buttons; QRect _pinOn; QRect _pinOff; + QRect _back; QRect _muteOn; QRect _muteOff; diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp index 02b8d9aa93..f2dfabd91b 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_raster.cpp @@ -122,18 +122,28 @@ void Viewport::Renderer::paintTileControls( p.setClipRect(x, y, width, height); const auto guard = gsl::finally([&] { p.setClipping(false); }); - // Pin. const auto wide = _owner->wide(); if (wide) { - const auto inner = tile->pinInner(); + // Pin. + + const auto pinInner = tile->pinInner(); VideoTile::PaintPinButton( p, tile->pinned(), - x + inner.x(), - y + inner.y(), + x + pinInner.x(), + y + pinInner.y(), _owner->widget()->width(), &_pinBackground, &_pinIcon); + + // Back. + const auto backInner = tile->backInner(); + VideoTile::PaintBackButton( + p, + x + backInner.x(), + y + backInner.y(), + _owner->widget()->width(), + &_pinBackground); } const auto shown = _owner->_controlsShownRatio; diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp index c601c2a759..049194ea11 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp @@ -31,19 +31,27 @@ Viewport::VideoTile::VideoTile( setup(std::move(pinned)); } -QRect Viewport::VideoTile::pinInner() const { - return _pinInner.translated(0, -pinSlide()); -} - QRect Viewport::VideoTile::pinOuter() const { return _pinOuter; } -int Viewport::VideoTile::pinSlide() const { +QRect Viewport::VideoTile::pinInner() const { + return _pinInner.translated(0, -topControlsSlide()); +} + +QRect Viewport::VideoTile::backOuter() const { + return _backOuter; +} + +QRect Viewport::VideoTile::backInner() const { + return _backInner.translated(0, -topControlsSlide()); +} + +int Viewport::VideoTile::topControlsSlide() const { return anim::interpolate( st::groupCallVideoTile.pinPosition.y() + _pinInner.height(), 0, - _pinShownAnimation.value(_pinShown ? 1. : 0.)); + _topControlsShownAnimation.value(_topControlsShown ? 1. : 0.)); } bool Viewport::VideoTile::screencast() const { @@ -52,15 +60,15 @@ bool Viewport::VideoTile::screencast() const { void Viewport::VideoTile::setGeometry(QRect geometry) { _geometry = geometry; - updatePinnedGeometry(); + updateTopControlsGeometry(); } -void Viewport::VideoTile::togglePinShown(bool shown) { - if (_pinShown == shown) { +void Viewport::VideoTile::toggleTopControlsShown(bool shown) { + if (_topControlsShown == shown) { return; } - _pinShown = shown; - _pinShownAnimation.start( + _topControlsShown = shown; + _topControlsShownAnimation.start( _update, shown ? 0. : 1., shown ? 1. : 0., @@ -128,19 +136,68 @@ void Viewport::VideoTile::PaintPinButton( } -void Viewport::VideoTile::updatePinnedGeometry() { +QSize Viewport::VideoTile::BackInnerSize() { const auto &st = st::groupCallVideoTile; - const auto buttonSize = PinInnerSize(_pinned); - const auto fullWidth = st.pinPosition.x() * 2 + buttonSize.width(); - const auto fullHeight = st.pinPosition.y() * 2 + buttonSize.height(); - _pinInner = QRect(QPoint(), buttonSize).translated( - _geometry.width() - st.pinPosition.x() - buttonSize.width(), + const auto &icon = st::groupCallVideoTile.back; + const auto innerWidth = icon.width() + + st.pinTextPosition.x() + + st::semiboldFont->width(tr::lng_create_group_back(tr::now)); + 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(); + return { buttonWidth, buttonHeight }; +} + +void Viewport::VideoTile::PaintBackButton( + Painter &p, + int x, + int y, + int outerWidth, + not_null background) { + const auto &st = st::groupCallVideoTile; + const auto rect = QRect(QPoint(x, y), BackInnerSize()); + background->paint(p, rect); + st.back.paint( + p, + rect.marginsRemoved(st.pinPadding).topLeft(), + outerWidth); + p.setPen(st::groupCallVideoTextFg); + p.setFont(st::semiboldFont); + p.drawTextLeft( + (x + + st.pinPadding.left() + + st::groupCallVideoTile.pin.icon.width() + + st.pinTextPosition.x()), + (y + + st.pinPadding.top() + + st.pinTextPosition.y()), + outerWidth, + tr::lng_create_group_back(tr::now)); +} + +void Viewport::VideoTile::updateTopControlsGeometry() { + const auto &st = st::groupCallVideoTile; + + const auto pinSize = PinInnerSize(_pinned); + const auto pinWidth = st.pinPosition.x() * 2 + pinSize.width(); + const auto pinHeight = st.pinPosition.y() * 2 + pinSize.height(); + _pinInner = QRect(QPoint(), pinSize).translated( + _geometry.width() - st.pinPosition.x() - pinSize.width(), st.pinPosition.y()); _pinOuter = QRect( - _geometry.width() - fullWidth, + _geometry.width() - pinWidth, 0, - fullWidth, - fullHeight); + pinWidth, + pinHeight); + const auto backSize = BackInnerSize(); + const auto backWidth = st.pinPosition.x() * 2 + backSize.width(); + const auto backHeight = st.pinPosition.y() * 2 + backSize.height(); + _backInner = QRect(QPoint(), backSize).translated(st.pinPosition); + _backOuter = QRect(0, 0, backWidth, backHeight); } void Viewport::VideoTile::setup(rpl::producer pinned) { @@ -150,7 +207,7 @@ void Viewport::VideoTile::setup(rpl::producer pinned) { return (_pinned != pinned); }) | rpl::start_with_next([=](bool pinned) { _pinned = pinned; - updatePinnedGeometry(); + updateTopControlsGeometry(); _update(); }, _lifetime); diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h index 703ef3e822..6695eff057 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h @@ -43,6 +43,8 @@ public: } [[nodiscard]] QRect pinOuter() const; [[nodiscard]] QRect pinInner() const; + [[nodiscard]] QRect backOuter() const; + [[nodiscard]] QRect backInner() const; [[nodiscard]] const VideoEndpoint &endpoint() const { return _endpoint; } @@ -55,7 +57,7 @@ public: [[nodiscard]] bool screencast() const; void setGeometry(QRect geometry); - void togglePinShown(bool shown); + void toggleTopControlsShown(bool shown); bool updateRequestedQuality(VideoQuality quality); [[nodiscard]] rpl::lifetime &lifetime() { @@ -72,10 +74,18 @@ public: not_null background, not_null icon); + [[nodiscard]] static QSize BackInnerSize(); + static void PaintBackButton( + Painter &p, + int x, + int y, + int outerWidth, + not_null background); + private: void setup(rpl::producer pinned); - [[nodiscard]] int pinSlide() const; - void updatePinnedGeometry(); + [[nodiscard]] int topControlsSlide() const; + void updateTopControlsGeometry(); const VideoEndpoint _endpoint; const Fn _update; @@ -85,8 +95,10 @@ private: rpl::variable _trackSize; QRect _pinOuter; QRect _pinInner; - Ui::Animations::Simple _pinShownAnimation; - bool _pinShown = false; + QRect _backOuter; + QRect _backInner; + Ui::Animations::Simple _topControlsShownAnimation; + bool _topControlsShown = false; bool _pinned = false; std::optional _quality;