diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 4b5530416..6cfb4a2bb 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -776,6 +776,10 @@ void PeerListContent::appendRow(std::unique_ptr row) { if (_rowsById.find(row->id()) == _rowsById.cend()) { row->setAbsoluteIndex(_rows.size()); addRowEntry(row.get()); + if (!_hiddenRows.empty()) { + Assert(!row->hidden()); + _filterResults.push_back(row.get()); + } _rows.push_back(std::move(row)); } } @@ -813,6 +817,17 @@ void PeerListContent::changeCheckState( [=] { updateRow(row); }); } +void PeerListContent::setRowHidden(not_null row, bool hidden) { + Expects(!row->isSearchResult()); + + row->setHidden(hidden); + if (hidden) { + _hiddenRows.emplace(row); + } else { + _hiddenRows.remove(row); + } +} + void PeerListContent::addRowEntry(not_null row) { if (_controller->respectSavedMessagesChat() && !row->special()) { if (row->peer()->isSelf()) { @@ -879,6 +894,10 @@ void PeerListContent::prependRow(std::unique_ptr row) { if (_rowsById.find(row->id()) == _rowsById.cend()) { addRowEntry(row.get()); + if (!_hiddenRows.empty()) { + Assert(!row->hidden()); + _filterResults.insert(_filterResults.begin(), row.get()); + } _rows.insert(_rows.begin(), std::move(row)); refreshIndices(); } @@ -894,6 +913,10 @@ void PeerListContent::prependRowFromSearchResult(not_null row) { Assert(_searchRows[index].get() == row); row->setIsSearchResult(false); + if (!_hiddenRows.empty()) { + Assert(!row->hidden()); + _filterResults.insert(_filterResults.begin(), row); + } _rows.insert(_rows.begin(), std::move(_searchRows[index])); refreshIndices(); removeRowAtIndex(_searchRows, index); @@ -947,6 +970,7 @@ void PeerListContent::removeRow(not_null row) { _filterResults.erase( ranges::remove(_filterResults, row), end(_filterResults)); + _hiddenRows.remove(row); removeRowAtIndex(eraseFrom, index); restoreSelection(); @@ -984,7 +1008,9 @@ void PeerListContent::convertRowToSearchResult(not_null row) { removeFromSearchIndex(row); row->setIsSearchResult(true); + row->setHidden(false); row->setAbsoluteIndex(_searchRows.size()); + _hiddenRows.remove(row); _searchRows.push_back(std::move(_rows[index])); removeRowAtIndex(_rows, index); } @@ -1069,6 +1095,14 @@ int PeerListContent::labelHeight() const { } void PeerListContent::refreshRows() { + if (!_hiddenRows.empty()) { + _filterResults.clear(); + for (const auto &row : _rows) { + if (!row->hidden()) { + _filterResults.push_back(row.get()); + } + } + } resizeToWidth(width()); if (_visibleBottom > 0) { checkScrollForPreload(); @@ -1082,7 +1116,7 @@ void PeerListContent::refreshRows() { void PeerListContent::setSearchMode(PeerListSearchMode mode) { if (_searchMode != mode) { if (!addingToSearchIndex()) { - for_const (auto &row, _rows) { + for (const auto &row : _rows) { addToSearchIndex(row.get()); } } @@ -1284,13 +1318,22 @@ void PeerListContent::mousePressReleased(Qt::MouseButton button) { void PeerListContent::showRowMenu( not_null row, + bool highlightRow, Fn)> destroyed) { - showRowMenu(findRowIndex(row), QCursor::pos(), std::move(destroyed)); + const auto index = findRowIndex(row); + showRowMenu( + index, + row, + QCursor::pos(), + highlightRow, + std::move(destroyed)); } bool PeerListContent::showRowMenu( RowIndex index, + PeerListRow *row, QPoint globalPos, + bool highlightRow, Fn)> destroyed) { if (_contextMenu) { _contextMenu->setDestroyedCallback(nullptr); @@ -1301,7 +1344,9 @@ bool PeerListContent::showRowMenu( mousePressReleased(_pressButton); } - const auto row = getRow(index); + if (highlightRow) { + row = getRow(index); + } if (!row) { return false; } @@ -1312,11 +1357,15 @@ bool PeerListContent::showRowMenu( return false; } - setContexted({ index, false }); + if (highlightRow) { + setContexted({ index, false }); + } raw->setDestroyedCallback(crl::guard( this, [=] { - setContexted(Selected()); + if (highlightRow) { + setContexted(Selected()); + } handleMouseMove(QCursor::pos()); if (destroyed) { destroyed(raw); @@ -1330,7 +1379,7 @@ void PeerListContent::contextMenuEvent(QContextMenuEvent *e) { if (e->reason() == QContextMenuEvent::Mouse) { handleMouseMove(e->globalPos()); } - if (showRowMenu(_selected.index, e->globalPos())) { + if (showRowMenu(_selected.index, nullptr, e->globalPos(), true)) { e->accept(); } } @@ -1607,6 +1656,8 @@ void PeerListContent::searchQueryChanged(QString query) { if (_normalizedSearchQuery != normalizedQuery) { setSearchQuery(query, normalizedQuery); if (_controller->searchInLocal() && !searchWordsList.isEmpty()) { + Assert(_hiddenRows.empty()); + auto minimalList = (const std::vector>*)nullptr; for (const auto &searchWord : searchWordsList) { auto searchWordStart = searchWord[0].toLower(); @@ -1656,15 +1707,17 @@ void PeerListContent::searchQueryChanged(QString query) { } std::unique_ptr PeerListContent::saveState() const { + Expects(_hiddenRows.empty()); + auto result = std::make_unique(); result->controllerState = std::make_unique(); result->list.reserve(_rows.size()); - for (auto &row : _rows) { + for (const auto &row : _rows) { result->list.push_back(row->peer()); } result->filterResults.reserve(_filterResults.size()); - for (auto &row : _filterResults) { + for (const auto &row : _filterResults) { result->filterResults.push_back(row->peer()); } result->searchQuery = _searchQuery; @@ -1844,8 +1897,7 @@ void PeerListContent::updateRow(RowIndex index) { if (index.value < 0) { return; } - auto row = getRow(index); - if (row->disabled()) { + if (const auto row = getRow(index); row && row->disabled()) { if (index == _selected.index) { setSelected(Selected()); } @@ -1940,3 +1992,10 @@ PeerListContent::~PeerListContent() { _contextMenu->setDestroyedCallback(nullptr); } } + +void PeerListContentDelegate::peerListShowRowMenu( + not_null row, + bool highlightRow, + Fn)> destroyed) { + _content->showRowMenu(row, highlightRow, std::move(destroyed)); +} \ No newline at end of file diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 44b52d5ec..f9aeb42c6 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -161,7 +161,7 @@ public: template void setChecked( bool checked, - const style::RoundImageCheckbox &st, + const style::RoundImageCheckbox &st, anim::type animated, UpdateCallback callback) { if (checked && !_checkbox) { @@ -169,6 +169,12 @@ public: } setCheckedInternal(checked, animated); } + void setHidden(bool hidden) { + _hidden = hidden; + } + [[nodiscard]] bool hidden() const { + return _hidden; + } void finishCheckedAnimation(); void invalidatePixmapsCache(); @@ -237,6 +243,7 @@ private: base::flat_set _nameFirstLetters; int _absoluteIndex = -1; State _disabledState = State::Active; + bool _hidden = false; bool _initialized : 1; bool _isSearchResult : 1; bool _isSavedMessagesChat : 1; @@ -274,6 +281,7 @@ public: virtual void peerListConvertRowToSearchResult(not_null row) = 0; virtual bool peerListIsRowChecked(not_null row) = 0; virtual void peerListSetRowChecked(not_null row, bool checked) = 0; + virtual void peerListSetRowHidden(not_null row, bool hidden) = 0; virtual void peerListSetForeignRowChecked( not_null row, bool checked, @@ -304,6 +312,7 @@ public: virtual void peerListShowRowMenu( not_null row, + bool highlightRow, Fn)> destroyed = nullptr) = 0; virtual int peerListSelectedRowsCount() = 0; virtual std::unique_ptr peerListSaveState() const = 0; @@ -577,6 +586,9 @@ public: not_null row, bool checked, anim::type animated); + void setRowHidden( + not_null row, + bool hidden); template void reorderRows(ReorderCallback &&callback) { @@ -585,6 +597,9 @@ public: callback(searchEntity.second.begin(), searchEntity.second.end()); } refreshIndices(); + if (!_hiddenRows.empty()) { + callback(_filterResults.begin(), _filterResults.end()); + } update(); } @@ -593,6 +608,7 @@ public: void showRowMenu( not_null row, + bool highlightRow, Fn)> destroyed); auto scrollToRequests() const { @@ -680,7 +696,9 @@ private: bool showRowMenu( RowIndex index, + PeerListRow *row, QPoint globalPos, + bool highlightRow, Fn)> destroyed = nullptr); crl::time paintRow(Painter &p, crl::time now, RowIndex index); @@ -691,7 +709,7 @@ private: void removeFromSearchIndex(not_null row); void setSearchQuery(const QString &query, const QString &normalizedQuery); bool showingSearch() const { - return !_searchQuery.isEmpty(); + return !_hiddenRows.empty() || !_searchQuery.isEmpty(); } int shownRowsCount() const { return showingSearch() ? _filterResults.size() : _rows.size(); @@ -737,6 +755,7 @@ private: QString _normalizedSearchQuery; QString _mentionHighlight; std::vector> _filterResults; + base::flat_set> _hiddenRows; int _aboveHeight = 0; int _belowHeight = 0; @@ -801,6 +820,11 @@ public: bool checked) override { _content->changeCheckState(row, checked, anim::type::normal); } + void peerListSetRowHidden( + not_null row, + bool hidden) override { + _content->setRowHidden(row, hidden); + } void peerListSetForeignRowChecked( not_null row, bool checked, @@ -871,10 +895,9 @@ public: _content->restoreState(std::move(state)); } void peerListShowRowMenu( - not_null row, - Fn)> destroyed = nullptr) override { - _content->showRowMenu(row, std::move(destroyed)); - } + not_null row, + bool highlightRow, + Fn)> destroyed = nullptr) override; protected: not_null content() const { diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp index 5d2bb6ef9..093c5c5ba 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp @@ -533,7 +533,7 @@ void LinksController::rowClicked(not_null row) { } void LinksController::rowActionClicked(not_null row) { - delegate()->peerListShowRowMenu(row, nullptr); + delegate()->peerListShowRowMenu(row, true); } base::unique_qptr LinksController::rowContextMenu( diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 5db3eb4cf..343d52066 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -446,7 +446,7 @@ void Panel::refreshIncomingGeometry() { to, Qt::KeepAspectRatioByExpanding); - // If we cut out no more than 0.33 of the original, let's use expanding. + // If we cut out no more than 0.25 of the original, let's use expanding. const auto use = ((big.width() * 3 <= to.width() * 4) && (big.height() * 3 <= to.height() * 4)) ? big diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 6ebf8a3ba..4a0bc193d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -529,11 +529,15 @@ void GroupCall::toggleScreenSharing(std::optional uniqueId) { } bool GroupCall::hasVideoWithFrames() const { - return _hasVideoWithFrames.current(); + return !_shownVideoTracks.empty(); } rpl::producer GroupCall::hasVideoWithFramesValue() const { - return _hasVideoWithFrames.value(); + return _videoStreamShownUpdates.events_starting_with( + VideoActiveToggle() + ) | rpl::map([=] { + return hasVideoWithFrames(); + }) | rpl::distinct_until_changed(); } void GroupCall::setScheduledDate(TimeId date) { @@ -586,8 +590,16 @@ void GroupCall::subscribeToReal(not_null real) { ? regularEndpoint(data.now->cameraEndpoint()) : EmptyString(); if (wasCameraEndpoint != nowCameraEndpoint) { - markEndpointActive({ peer, nowCameraEndpoint }, true); - markEndpointActive({ peer, wasCameraEndpoint }, false); + markEndpointActive({ + VideoEndpointType::Camera, + peer, + nowCameraEndpoint + }, true); + markEndpointActive({ + VideoEndpointType::Camera, + peer, + wasCameraEndpoint + }, false); } const auto &wasScreenEndpoint = data.was ? regularEndpoint(data.was->screenEndpoint()) @@ -596,8 +608,16 @@ void GroupCall::subscribeToReal(not_null real) { ? regularEndpoint(data.now->screenEndpoint()) : EmptyString(); if (wasScreenEndpoint != nowScreenEndpoint) { - markEndpointActive({ peer, nowScreenEndpoint }, true); - markEndpointActive({ peer, wasScreenEndpoint }, false); + markEndpointActive({ + VideoEndpointType::Screen, + peer, + nowScreenEndpoint + }, true); + markEndpointActive({ + VideoEndpointType::Screen, + peer, + wasScreenEndpoint + }, false); } }, _lifetime); @@ -792,14 +812,22 @@ void GroupCall::setScreenEndpoint(std::string endpoint) { return; } if (!_screenEndpoint.empty()) { - markEndpointActive({ _joinAs, _screenEndpoint }, false); + markEndpointActive({ + VideoEndpointType::Screen, + _joinAs, + _screenEndpoint + }, false); } _screenEndpoint = std::move(endpoint); if (_screenEndpoint.empty()) { return; } if (isSharingScreen()) { - markEndpointActive({ _joinAs, _screenEndpoint }, true); + markEndpointActive({ + VideoEndpointType::Screen, + _joinAs, + _screenEndpoint + }, true); } } @@ -808,14 +836,22 @@ void GroupCall::setCameraEndpoint(std::string endpoint) { return; } if (!_cameraEndpoint.empty()) { - markEndpointActive({ _joinAs, _cameraEndpoint }, false); + markEndpointActive({ + VideoEndpointType::Camera, + _joinAs, + _cameraEndpoint + }, false); } _cameraEndpoint = std::move(endpoint); if (_cameraEndpoint.empty()) { return; } if (isSharingCamera()) { - markEndpointActive({ _joinAs, _cameraEndpoint }, true); + markEndpointActive({ + VideoEndpointType::Camera, + _joinAs, + _cameraEndpoint + }, true); } } @@ -848,7 +884,7 @@ void GroupCall::markEndpointActive(VideoEndpoint endpoint, bool active) { if (!changed) { return; } - auto hasVideoWithFrames = _hasVideoWithFrames.current(); + auto shown = false; if (active) { const auto i = _activeVideoTracks.emplace( endpoint, @@ -862,11 +898,11 @@ void GroupCall::markEndpointActive(VideoEndpoint endpoint, bool active) { track->renderNextFrame( ) | rpl::start_with_next([=] { if (!track->frameSize().isEmpty()) { - _hasVideoWithFrames = true; + markTrackShown(endpoint, true); } }, i->second.lifetime); if (!track->frameSize().isEmpty()) { - hasVideoWithFrames = true; + shown = true; } addVideoOutput(i->first.id, { track->sink() }); } else { @@ -874,17 +910,17 @@ void GroupCall::markEndpointActive(VideoEndpoint endpoint, bool active) { _videoEndpointPinned = VideoEndpoint(); } _activeVideoTracks.erase(i); - hasVideoWithFrames = false; - for (const auto &[endpoint, track] : _activeVideoTracks) { - if (!track.track->frameSize().isEmpty()) { - hasVideoWithFrames = true; - break; - } - } } updateRequestedVideoChannelsDelayed(); - _videoStreamActiveUpdates.fire(std::move(endpoint)); - _hasVideoWithFrames = hasVideoWithFrames; + _videoStreamActiveUpdates.fire({ endpoint, active }); + markTrackShown(endpoint, shown); +} + +void GroupCall::markTrackShown(const VideoEndpoint &endpoint, bool shown) { + if ((shown && _shownVideoTracks.emplace(endpoint).second) + || (!shown && _shownVideoTracks.remove(endpoint))) { + _videoStreamShownUpdates.fire_copy({ endpoint, shown }); + } } void GroupCall::rejoin() { @@ -1746,7 +1782,11 @@ void GroupCall::ensureOutgoingVideo() { _cameraCapture->setState(tgcalls::VideoState::Inactive); } _isSharingCamera = active; - markEndpointActive({ _joinAs, _cameraEndpoint }, active); + markEndpointActive({ + VideoEndpointType::Camera, + _joinAs, + _cameraEndpoint + }, active); sendSelfUpdate(SendUpdateType::VideoMuted); applyMeInCallLocally(); }, _lifetime); @@ -1785,7 +1825,11 @@ void GroupCall::ensureOutgoingVideo() { _screenCapture->setState(tgcalls::VideoState::Inactive); } _isSharingScreen = active; - markEndpointActive({ _joinAs, _screenEndpoint }, active); + markEndpointActive({ + VideoEndpointType::Screen, + _joinAs, + _screenEndpoint + }, active); _screenJoinState.nextActionPending = true; checkNextJoinAction(); }, _lifetime); @@ -2172,22 +2216,23 @@ void GroupCall::fillActiveVideoEndpoints() { markEndpointActive(std::move(endpoint), true); } }; + using Type = VideoEndpointType; for (const auto &participant : participants) { const auto camera = participant.cameraEndpoint(); if (camera != _cameraEndpoint && camera != _screenEndpoint && participant.peer != _joinAs) { - feedOne({ participant.peer, camera }); + feedOne({ Type::Camera, participant.peer, camera }); } const auto screen = participant.screenEndpoint(); if (screen != _cameraEndpoint && screen != _screenEndpoint && participant.peer != _joinAs) { - feedOne({ participant.peer, screen }); + feedOne({ Type::Screen, participant.peer, screen }); } } - feedOne({ _joinAs, cameraSharingEndpoint() }); - feedOne({ _joinAs, screenSharingEndpoint() }); + feedOne({ Type::Camera, _joinAs, cameraSharingEndpoint() }); + feedOne({ Type::Screen, _joinAs, screenSharingEndpoint() }); if (pinned && !pinnedFound) { _videoEndpointPinned = VideoEndpoint(); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 83a19b471..8da0b23fe 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -76,7 +76,13 @@ struct LevelUpdate { bool me = false; }; +enum class VideoEndpointType { + Camera, + Screen, +}; + struct VideoEndpoint { + VideoEndpointType type = VideoEndpointType::Camera; PeerData *peer = nullptr; std::string id; @@ -130,6 +136,11 @@ struct VideoPinToggle { bool pinned = false; }; +struct VideoActiveToggle { + VideoEndpoint endpoint; + bool active = false; +}; + struct VideoQualityRequest { VideoEndpoint endpoint; Group::VideoQuality quality = Group::VideoQuality(); @@ -283,9 +294,13 @@ public: return _levelUpdates.events(); } [[nodiscard]] auto videoStreamActiveUpdates() const - -> rpl::producer { + -> rpl::producer { return _videoStreamActiveUpdates.events(); } + [[nodiscard]] auto videoStreamShownUpdates() const + -> rpl::producer { + return _videoStreamShownUpdates.events(); + } void pinVideoEndpoint(VideoEndpoint endpoint); void requestVideoQuality( const VideoEndpoint &endpoint, @@ -317,6 +332,10 @@ public: -> const base::flat_map & { return _activeVideoTracks; } + [[nodiscard]] auto shownVideoTracks() const + -> const base::flat_set & { + return _shownVideoTracks; + } [[nodiscard]] rpl::producer rejoinEvents() const { return _rejoinEvents.events(); } @@ -491,6 +510,7 @@ private: void addVideoOutput(const std::string &endpoint, SinkPointer sink); void markEndpointActive(VideoEndpoint endpoint, bool active); + void markTrackShown(const VideoEndpoint &endpoint, bool shown); [[nodiscard]] MTPInputGroupCall inputCall() const; @@ -560,10 +580,11 @@ private: bool _requireARGB32 = true; rpl::event_stream _levelUpdates; - rpl::event_stream _videoStreamActiveUpdates; + rpl::event_stream _videoStreamActiveUpdates; + rpl::event_stream _videoStreamShownUpdates; base::flat_map _activeVideoTracks; + base::flat_set _shownVideoTracks; rpl::variable _videoEndpointPinned; - rpl::variable _hasVideoWithFrames = false; base::flat_map _lastSpoke; rpl::event_stream _rejoinEvents; rpl::event_stream<> _allowedToSpeakNotifications; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 92b23ae91..2798725d6 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -171,7 +171,7 @@ private: // not_null row, // const std::string &endpoint); bool toggleRowVideo(not_null row); - void showRowMenu(not_null row); + void showRowMenu(not_null row, bool highlightRow); void toggleVideoEndpointActive( const VideoEndpoint &endpoint, @@ -180,6 +180,11 @@ private: void appendInvitedUsers(); void scheduleRaisedHandStatusRemove(); + void hideRowsWithVideoExcept(const VideoEndpoint &pinned); + void showAllHiddenRows(); + void hideRowWithVideo(const VideoEndpoint &endpoint); + void showRowWithVideo(const VideoEndpoint &endpoint); + const not_null _call; not_null _peer; std::string _largeEndpoint; @@ -412,6 +417,27 @@ void Members::Controller::setupListChangeViewers() { // } //}, _lifetime); + _call->videoEndpointPinnedValue( + ) | rpl::start_with_next([=](const VideoEndpoint &pinned) { + if (pinned) { + hideRowsWithVideoExcept(pinned); + } else { + showAllHiddenRows(); + } + }, _lifetime); + + _call->videoStreamShownUpdates( + ) | rpl::filter([=](const VideoActiveToggle &update) { + const auto &pinned = _call->videoEndpointPinned(); + return pinned && (update.endpoint != pinned); + }) | rpl::start_with_next([=](const VideoActiveToggle &update) { + if (update.active) { + hideRowWithVideo(update.endpoint); + } else { + showRowWithVideo(update.endpoint); + } + }, _lifetime); + _call->rejoinEvents( ) | rpl::start_with_next([=](const Group::RejoinEvent &event) { const auto guard = gsl::finally([&] { @@ -428,6 +454,59 @@ void Members::Controller::setupListChangeViewers() { }, _lifetime); } +void Members::Controller::hideRowsWithVideoExcept( + const VideoEndpoint &pinned) { + auto hidden = false; + for (const auto &endpoint : _call->shownVideoTracks()) { + if (endpoint != pinned) { + if (const auto row = findRow(endpoint.peer)) { + delegate()->peerListSetRowHidden(row, true); + hidden = true; + } + } + } + if (hidden) { + delegate()->peerListRefreshRows(); + } +} + +void Members::Controller::showAllHiddenRows() { + auto shown = false; + for (const auto &endpoint : _call->shownVideoTracks()) { + if (const auto row = findRow(endpoint.peer)) { + delegate()->peerListSetRowHidden(row, false); + shown = true; + } + } + if (shown) { + delegate()->peerListRefreshRows(); + } +} + +void Members::Controller::hideRowWithVideo(const VideoEndpoint &endpoint) { + if (const auto row = findRow(endpoint.peer)) { + delegate()->peerListSetRowHidden(row, true); + delegate()->peerListRefreshRows(); + } +} + +void Members::Controller::showRowWithVideo(const VideoEndpoint &endpoint) { + const auto peer = endpoint.peer; + const auto &pinned = _call->videoEndpointPinned(); + if (pinned) { + for (const auto &endpoint : _call->shownVideoTracks()) { + if (endpoint != pinned && endpoint.peer == peer) { + // Still hidden with another video. + return; + } + } + } + if (const auto row = findRow(endpoint.peer)) { + delegate()->peerListSetRowHidden(row, false); + delegate()->peerListRefreshRows(); + } +} + void Members::Controller::subscribeToChanges(not_null real) { _fullCount = real->fullCountValue(); @@ -463,9 +542,8 @@ void Members::Controller::subscribeToChanges(not_null real) { toggleVideoEndpointActive(endpoint, true); } _call->videoStreamActiveUpdates( - ) | rpl::start_with_next([=](const VideoEndpoint &endpoint) { - const auto active = _call->activeVideoTracks().contains(endpoint); - toggleVideoEndpointActive(endpoint, active); + ) | rpl::start_with_next([=](const VideoActiveToggle &update) { + toggleVideoEndpointActive(update.endpoint, update.active); }, _lifetime); if (_prepared) { @@ -822,9 +900,8 @@ Main::Session &Members::Controller::session() const { void Members::Controller::prepare() { delegate()->peerListSetSearchMode(PeerListSearchMode::Disabled); - //delegate()->peerListSetTitle(std::move(title)); - setDescriptionText(tr::lng_contacts_loading(tr::now)); - setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now)); + setDescription(nullptr); + setSearchNoResults(nullptr); if (const auto real = _call->lookupReal()) { prepareRows(real); @@ -1155,7 +1232,7 @@ bool Members::Controller::rowIsNarrow() { } void Members::Controller::rowShowContextMenu(not_null row) { - showRowMenu(row); + showRowMenu(row, false); } //void Members::Controller::rowPaintNarrowBackground( @@ -1253,12 +1330,14 @@ auto Members::Controller::kickParticipantRequests() const void Members::Controller::rowClicked(not_null row) { if (!toggleRowVideo(row)) { - showRowMenu(row); + showRowMenu(row, true); } } -void Members::Controller::showRowMenu(not_null row) { - delegate()->peerListShowRowMenu(row, [=](not_null menu) { +void Members::Controller::showRowMenu( + not_null row, + bool highlightRow) { + const auto cleanup = [=](not_null menu) { if (!_menu || _menu.get() != menu) { return; } @@ -1269,7 +1348,8 @@ void Members::Controller::showRowMenu(not_null row) { } } _menu = std::move(saved); - }); + }; + delegate()->peerListShowRowMenu(row, highlightRow, cleanup); } bool Members::Controller::toggleRowVideo(not_null row) { @@ -1317,7 +1397,7 @@ bool Members::Controller::toggleRowVideo(not_null row) { void Members::Controller::rowActionClicked( not_null row) { - showRowMenu(row); + showRowMenu(row, true); } base::unique_qptr Members::Controller::rowContextMenu( @@ -1414,6 +1494,7 @@ base::unique_qptr Members::Controller::createRowContextMenu( result->addAction( tr::lng_group_call_context_pin_camera(tr::now), [=] { _call->pinVideoEndpoint(VideoEndpoint{ + VideoEndpointType::Camera, participantPeer, camera }); }); } @@ -1427,6 +1508,7 @@ base::unique_qptr Members::Controller::createRowContextMenu( result->addAction( tr::lng_group_call_context_pin_screen(tr::now), [=] { _call->pinVideoEndpoint(VideoEndpoint{ + VideoEndpointType::Screen, participantPeer, screen }); }); } @@ -1828,7 +1910,7 @@ QRect Members::getInnerGeometry() const { 0, -_scroll->scrollTop(), width(), - _list->y() + _list->height() + add); + _list->y() + _list->height() + _bottomSkip->height() + add); } rpl::producer Members::fullCountValue() const { @@ -1837,18 +1919,38 @@ rpl::producer Members::fullCountValue() const { void Members::setupList() { _listController->setStyleOverrides(&st::groupCallMembersList); - _topSkip = _layout->add( - object_ptr( - _layout.get(), - st::groupCallMembersTopSkip)); - _topSkip->paintRequest( - ) | rpl::start_with_next([=](QRect clip) { - QPainter(_topSkip).fillRect(clip, st::groupCallMembersBg); - }, _topSkip->lifetime()); + const auto addSkip = [&] { + const auto result = _layout->add( + object_ptr( + _layout.get(), + st::groupCallMembersTopSkip)); + result->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + QPainter(result).fillRect(clip, st::groupCallMembersBg); + }, result->lifetime()); + return result; + }; + _topSkip = addSkip(); _list = _layout->add( object_ptr( _layout.get(), _listController.get())); + _bottomSkip = addSkip(); + + using namespace rpl::mappers; + rpl::combine( + _list->heightValue() | rpl::map(_1 > 0), + _addMemberButton.value() | rpl::map(_1 != nullptr) + ) | rpl::distinct_until_changed( + ) | rpl::start_with_next([=](bool hasList, bool hasAddMembers) { + _topSkip->resize( + _topSkip->width(), + hasList ? st::groupCallMembersTopSkip : 0); + _bottomSkip->resize( + _bottomSkip->width(), + (hasList && !hasAddMembers) ? st::groupCallMembersTopSkip : 0); + }, _list->lifetime()); + const auto skip = _layout->add(object_ptr(_layout.get())); _mode.value( ) | rpl::start_with_next([=](PanelMode mode) { @@ -1997,6 +2099,7 @@ void Members::setupFakeRoundCorners() { const auto bottom = top + _topSkip->height() + list.height() + + _bottomSkip->height() + addMembers - bottomleft->height(); topleft->move(left, top); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.h b/Telegram/SourceFiles/calls/group/calls_group_members.h index 737085b63..af85f7525 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members.h @@ -102,6 +102,7 @@ private: rpl::event_stream<> _enlargeVideoClicks; rpl::variable _addMemberButton = nullptr; RpWidget *_topSkip = nullptr; + RpWidget *_bottomSkip = nullptr; ListWidget *_list = nullptr; rpl::event_stream<> _addMemberRequests; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp index e4e4167bf..1e35d7a6d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp @@ -399,7 +399,7 @@ bool MembersRow::paintVideo( // .outer = QSize(sizew, sizeh) * cIntRetinaFactor(), //}; //const auto frame = _videoTrackShown->frame(request); - //auto copy = frame; // #TODO calls optimize. + //auto copy = frame; // TODO calls optimize. //copy.detach(); //if (mode == PanelMode::Default) { // Images::prepareCircle(copy); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members_row.h b/Telegram/SourceFiles/calls/group/calls_group_members_row.h index ea6ad9af9..0f94d2f50 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members_row.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members_row.h @@ -254,7 +254,7 @@ private: //std::unique_ptr _videoTrack; //Webrtc::VideoTrack *_videoTrackShown = nullptr; //std::string _videoTrackEndpoint; - //rpl::lifetime _videoTrackLifetime; // #TODO calls move to unique_ptr. + //rpl::lifetime _videoTrackLifetime; // TODO calls move to unique_ptr. Ui::Animations::Simple _speakingAnimation; // For gray-red/green icon. Ui::Animations::Simple _mutedAnimation; // For gray/red icon. Ui::Animations::Simple _activeAnimation; // For icon cross animation. diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index df42c6e98..9e0a4aeac 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -977,8 +977,14 @@ void Panel::setupMembers() { _startsWhen.destroy(); _members.create(widget(), _call, mode()); + setupVideo(_viewport.get()); setupVideo(_members->viewport()); + _viewport->mouseInsideValue( + ) | rpl::start_with_next([=](bool inside) { + toggleWideControls(inside); + }, _viewport->lifetime()); + _members->show(); refreshControlsBackground(); @@ -1135,9 +1141,10 @@ void Panel::setupVideo(not_null viewport) { setupTile(endpoint, track); } _call->videoStreamActiveUpdates( - ) | rpl::start_with_next([=](const VideoEndpoint &endpoint) { - if (_call->activeVideoTracks().contains(endpoint)) { + ) | rpl::start_with_next([=](const VideoActiveToggle &update) { + if (update.active) { // Add async (=> the participant row is definitely in Members). + const auto endpoint = update.endpoint; crl::on_main(viewport->widget(), [=] { const auto &tracks = _call->activeVideoTracks(); const auto i = tracks.find(endpoint); @@ -1147,7 +1154,7 @@ void Panel::setupVideo(not_null viewport) { }); } else { // Remove sync. - viewport->remove(endpoint); + viewport->remove(update.endpoint); } }, viewport->lifetime()); @@ -1168,11 +1175,6 @@ void Panel::setupVideo(not_null viewport) { ) | rpl::start_with_next([=](const VideoQualityRequest &request) { _call->requestVideoQuality(request.endpoint, request.quality); }, viewport->lifetime()); - - viewport->mouseInsideValue( - ) | rpl::start_with_next([=](bool inside) { - toggleWideControls(inside); - }, viewport->lifetime()); } void Panel::toggleWideControls(bool shown) { diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp index 73498c9ad..e71541208 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp @@ -26,8 +26,11 @@ constexpr auto kScaleForBlurTextureIndex = 3; constexpr auto kFirstBlurPassTextureIndex = 4; constexpr auto kBlurTextureSizeFactor = 1.7; constexpr auto kBlurOpacity = 0.7; +constexpr auto kMinCameraVisiblePart = 0.75; -ShaderPart FragmentBlurTexture(bool vertical, char prefix = 'v') { +[[nodiscard]] ShaderPart FragmentBlurTexture( + bool vertical, + char prefix = 'v') { const auto offsets = (vertical ? QString("0, 1") : QString("1, 0")); const auto name = prefix + QString("_texcoord"); return { @@ -94,6 +97,16 @@ vec4 background() { }; } +[[nodiscard]] bool UseExpandForCamera(QSize original, QSize viewport) { + const auto big = original.scaled( + viewport, + Qt::KeepAspectRatioByExpanding); + + // If we cut out no more than 0.25 of the original, let's use expanding. + return (big.width() * kMinCameraVisiblePart <= viewport.width()) + && (big.height() * kMinCameraVisiblePart <= viewport.height()); +} + [[nodiscard]] QSize NonEmpty(QSize size) { return QSize(std::max(size.width(), 1), std::max(size.height(), 1)); } @@ -440,12 +453,14 @@ void Viewport::RendererGL::paintTile( const auto unscaled = Media::View::FlipSizeByRotation( data.yuv420->size, data.rotation); + const auto tileSize = geometry.size(); const auto swap = (((data.rotation / 90) % 2) == 1); - const auto expand = !_owner->wide()/* && !tile->screencast()*/; - auto texCoords = CountTexCoords(unscaled, geometry.size(), expand, swap); + const auto expand = !tile->screencast() + && (!_owner->wide() || UseExpandForCamera(unscaled, tileSize)); + auto texCoords = CountTexCoords(unscaled, tileSize, expand, swap); auto blurTexCoords = expand ? texCoords - : CountTexCoords(unscaled, geometry.size(), true); + : CountTexCoords(unscaled, tileSize, true); const auto rect = transformRect(geometry); auto toBlurTexCoords = std::array, 4> { { { { 0.f, 1.f } }, diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp index 464cb3345..ee3445184 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.cpp @@ -46,6 +46,10 @@ int Viewport::VideoTile::pinSlide() const { _pinShownAnimation.value(_pinShown ? 1. : 0.)); } +bool Viewport::VideoTile::screencast() const { + return (_endpoint.type == VideoEndpointType::Screen); +} + void Viewport::VideoTile::setGeometry(QRect geometry) { _geometry = geometry; updatePinnedGeometry(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h index a63431d70..502edeef2 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_tile.h @@ -53,6 +53,7 @@ public: return _trackSize.value(); } + [[nodiscard]] bool screencast() const; void setGeometry(QRect geometry); void togglePinShown(bool shown); bool updateRequestedQuality(VideoQuality quality);