diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index e8a0ac59a..6e8c20db7 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -490,6 +490,7 @@ groupCallMenu: Menu(defaultMenu) { itemFgShortcutDisabled: groupCallMemberNotJoinedStatus; separatorFg: groupCallMenuBgOver; + separatorPadding: margins(0px, 4px, 0px, 4px); arrow: icon {{ "dropdown_submenu_arrow", groupCallMemberNotJoinedStatus }}; @@ -513,6 +514,12 @@ groupCallPopupMenu: PopupMenu(defaultPopupMenu) { menu: groupCallMenu; animation: groupCallPanelAnimation; } +groupCallPopupMenuWithVolume: PopupMenu(groupCallPopupMenu) { + scrollPadding: margins(0px, 3px, 0px, 8px); + menu: Menu(groupCallMenu) { + widthMin: 210px; + } +} groupCallRecordingTimerPadding: margins(0px, 4px, 0px, 4px); groupCallRecordingTimerFont: font(12px); @@ -1071,6 +1078,8 @@ groupCallMuteCrossLine: CrossLineAnimation { groupCallMenuSpeakerArcsSkip: 1px; groupCallMenuVolumeSkip: 5px; +groupCallMenuVolumePadding: margins(17px, 6px, 17px, 5px); +groupCallMenuVolumeMargin: margins(55px, 0px, 15px, 0px); groupCallMenuVolumeSlider: MediaSlider(defaultContinuousSlider) { activeFg: groupCallMembersFg; inactiveFg: groupCallMemberInactiveIcon; @@ -1078,6 +1087,8 @@ groupCallMenuVolumeSlider: MediaSlider(defaultContinuousSlider) { inactiveFgOver: groupCallMemberInactiveIcon; activeFgDisabled: groupCallMemberInactiveIcon; receivedTillFg: groupCallMemberInactiveIcon; + width: 7px; + seekSize: size(7px, 7px); } groupCallSpeakerArcsAnimation: ArcsAnimation { diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index c5a2ea3fb..4fc184128 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -1236,12 +1236,10 @@ base::unique_qptr Members::Controller::createRowContextMenu( not_null row) { const auto participantPeer = row->peer(); const auto real = static_cast(row.get()); - - auto result = base::make_unique_q( - parent, - st::groupCallPopupMenu); - const auto muteState = real->state(); + const auto muted = (muteState == Row::State::Muted) + || (muteState == Row::State::RaisedHand); + const auto addVolumeItem = !muted || isMe(participantPeer); const auto admin = IsGroupCallAdmin(_peer, participantPeer); const auto session = &_peer->session(); const auto getCurrentWindow = [=]() -> Window::SessionController* { @@ -1262,6 +1260,12 @@ base::unique_qptr Members::Controller::createRowContextMenu( } return getCurrentWindow(); }; + + auto result = base::make_unique_q( + parent, + (addVolumeItem + ? st::groupCallPopupMenuWithVolume + : st::groupCallPopupMenu)); const auto weakMenu = Ui::MakeWeak(result.get()); const auto performOnMainWindow = [=](auto callback) { if (const auto window = getWindow()) { @@ -1442,7 +1446,8 @@ void Members::Controller::addMuteActionsToContextMenu( auto mutesFromVolume = rpl::never() | rpl::type_erased(); - if (!muted || _call->joinAs() == participantPeer) { + const auto addVolumeItem = !muted || isMe(participantPeer); + if (addVolumeItem) { auto otherParticipantStateValue = _call->otherParticipantStateValue( ) | rpl::filter([=](const Group::ParticipantState &data) { @@ -1451,7 +1456,7 @@ void Members::Controller::addMuteActionsToContextMenu( auto volumeItem = base::make_unique_q( menu->menu(), - st::groupCallPopupMenu.menu, + st::groupCallPopupMenuWithVolume.menu, otherParticipantStateValue, row->volume(), Group::kMaxVolume, @@ -1491,6 +1496,10 @@ void Members::Controller::addMuteActionsToContextMenu( }, volumeItem->lifetime()); menu->addAction(std::move(volumeItem)); + + if (!isMe(participantPeer)) { + menu->addSeparator(); + } }; const auto muteAction = [&]() -> QAction* { diff --git a/Telegram/SourceFiles/calls/group/calls_volume_item.cpp b/Telegram/SourceFiles/calls/group/calls_volume_item.cpp index 799814d9d..71ad8cffd 100644 --- a/Telegram/SourceFiles/calls/group/calls_volume_item.cpp +++ b/Telegram/SourceFiles/calls/group/calls_volume_item.cpp @@ -36,10 +36,6 @@ constexpr auto kVolumeStickedValues = { 175. / kMaxVolumePercent, 2. / kMaxVolumePercent }, }}; -QString VolumeString(int volumePercent) { - return u"%1%"_q.arg(volumePercent); -} - } // namespace MenuVolumeItem::MenuVolumeItem( @@ -75,20 +71,21 @@ MenuVolumeItem::MenuVolumeItem( sizeValue( ) | rpl::start_with_next([=](const QSize &size) { const auto geometry = QRect(QPoint(), size); - _itemRect = geometry - _st.itemPadding; + _itemRect = geometry - st::groupCallMenuVolumePadding; _speakerRect = QRect(_itemRect.topLeft(), _stCross.icon.size()); _arcPosition = _speakerRect.center() + QPoint(0, st::groupCallMenuSpeakerArcsSkip); - _volumeRect = QRect( - _arcPosition.x() - + st::groupCallMenuVolumeSkip - + _arcs->finishedWidth(), + const auto sliderLeft = _arcPosition.x() + + st::groupCallMenuVolumeSkip + + _arcs->maxWidth() + + st::groupCallMenuVolumeSkip; + _slider->setGeometry( + st::groupCallMenuVolumeMargin.left(), _speakerRect.y(), - _st.itemStyle.font->width(VolumeString(kMaxVolumePercent)), + (geometry.width() + - st::groupCallMenuVolumeMargin.left() + - st::groupCallMenuVolumeMargin.right()), _speakerRect.height()); - - _slider->setGeometry(_itemRect - - style::margins(0, contentHeight() / 2, 0, 0)); }, lifetime()); setCloudVolume(startVolume); @@ -110,15 +107,12 @@ MenuVolumeItem::MenuVolumeItem( unmuteColor(), muteColor(), muteProgress); - p.setPen(mutePen); - p.setFont(_st.itemStyle.font); - p.drawText(_volumeRect, VolumeString(volume), style::al_left); _crossLineMute->paint( p, _speakerRect.topLeft(), muteProgress, - (!muteProgress) ? std::nullopt : std::optional(mutePen)); + (muteProgress > 0) ? std::make_optional(mutePen) : std::nullopt); { p.translate(_arcPosition); @@ -133,7 +127,7 @@ MenuVolumeItem::MenuVolumeItem( _toggleMuteLocallyRequests.fire_copy(newMuted); _crossLineAnimation.start( - [=] { update(_speakerRect.united(_volumeRect)); }, + [=] { update(_speakerRect); }, _localMuted ? 0. : 1., _localMuted ? 1. : 0., st::callPanelDuration); @@ -141,8 +135,8 @@ MenuVolumeItem::MenuVolumeItem( if (value > 0) { _changeVolumeLocallyRequests.fire(value * _maxVolume); } - update(_volumeRect); _arcs->setValue(value); + updateSliderColor(value); }); const auto returnVolume = [=] { @@ -169,6 +163,7 @@ MenuVolumeItem::MenuVolumeItem( if (!_cloudMuted && !muted) { _changeVolumeRequests.fire_copy(newVolume); } + updateSliderColor(value); }); std::move( @@ -209,30 +204,15 @@ MenuVolumeItem::MenuVolumeItem( } void MenuVolumeItem::initArcsAnimation() { - const auto volumeLeftWas = lifetime().make_state(0); const auto lastTime = lifetime().make_state(0); _arcsAnimation.init([=](crl::time now) { _arcs->update(now); update(_speakerRect); - - const auto wasRect = _volumeRect; - _volumeRect.moveLeft(anim::interpolate( - *volumeLeftWas, - _arcPosition.x() - + st::groupCallMenuVolumeSkip - + _arcs->finishedWidth(), - std::clamp( - (now - (*lastTime)) - / float64(st::groupCallSpeakerArcsAnimation.duration), - 0., - 1.))); - update(_speakerRect.united(wasRect.united(_volumeRect))); }); _arcs->startUpdateRequests( ) | rpl::start_with_next([=] { if (!_arcsAnimation.animating()) { - *volumeLeftWas = _volumeRect.left(); *lastTime = crl::now(); _arcsAnimation.start(); } @@ -269,8 +249,30 @@ void MenuVolumeItem::setCloudVolume(int volume) { } void MenuVolumeItem::setSliderVolume(int volume) { - _slider->setValue(float64(volume) / _maxVolume); - update(_volumeRect); + const auto value = float64(volume) / _maxVolume; + _slider->setValue(value); + updateSliderColor(value); +} + +void MenuVolumeItem::updateSliderColor(float64 value) { + value = std::clamp(value, 0., 1.); + const auto color = [](int rgb) { + return QColor( + int((rgb & 0xFF0000) >> 16), + int((rgb & 0x00FF00) >> 8), + int(rgb & 0x0000FF)); + }; + const auto colors = std::array{ { + color(0xF66464), + color(0xD0B738), + color(0x24CD80), + color(0x3BBCEC), + } }; + _slider->setActiveFgOverride((value < 0.25) + ? anim::color(colors[0], colors[1], value / 0.25) + : (value < 0.5) + ? anim::color(colors[1], colors[2], (value - 0.25) / 0.25) + : anim::color(colors[2], colors[3], (value - 0.5) / 0.5)); } not_null MenuVolumeItem::action() const { @@ -282,9 +284,9 @@ bool MenuVolumeItem::isEnabled() const { } int MenuVolumeItem::contentHeight() const { - return _st.itemPadding.top() - + _st.itemPadding.bottom() - + _stCross.icon.height() * 2; + return st::groupCallMenuVolumePadding.top() + + st::groupCallMenuVolumePadding.bottom() + + _stCross.icon.height(); } rpl::producer MenuVolumeItem::toggleMuteRequests() const { diff --git a/Telegram/SourceFiles/calls/group/calls_volume_item.h b/Telegram/SourceFiles/calls/group/calls_volume_item.h index 5ba925835..86eadd1f9 100644 --- a/Telegram/SourceFiles/calls/group/calls_volume_item.h +++ b/Telegram/SourceFiles/calls/group/calls_volume_item.h @@ -52,6 +52,7 @@ private: void setCloudVolume(int volume); void setSliderVolume(int volume); + void updateSliderColor(float64 value); QColor unmuteColor() const; QColor muteColor() const; @@ -64,7 +65,6 @@ private: QRect _itemRect; QRect _speakerRect; - QRect _volumeRect; QPoint _arcPosition; const base::unique_qptr _slider; diff --git a/Telegram/SourceFiles/ui/widgets/continuous_sliders.cpp b/Telegram/SourceFiles/ui/widgets/continuous_sliders.cpp index e98a7a991..5161405ef 100644 --- a/Telegram/SourceFiles/ui/widgets/continuous_sliders.cpp +++ b/Telegram/SourceFiles/ui/widgets/continuous_sliders.cpp @@ -223,6 +223,11 @@ void MediaSlider::addDivider(float64 atValue, const QSize &size) { _dividers.push_back(Divider{ atValue, size }); } +void MediaSlider::setActiveFgOverride(std::optional color) { + _activeFgOverride = color; + update(); +} + void MediaSlider::paintEvent(QPaintEvent *e) { if (_paintDisabled) { return; @@ -250,17 +255,27 @@ void MediaSlider::paintEvent(QPaintEvent *e) { : value; const auto markerFrom = (horizontal ? seekRect.x() : seekRect.y()); - const auto markerLength = (horizontal ? seekRect.width() : seekRect.height()); + const auto markerLength = horizontal + ? seekRect.width() + : seekRect.height(); const auto from = 0; const auto length = (horizontal ? width() : height()); const auto mid = qRound(from + value * length); const auto till = std::max(mid, qRound(from + receivedTill * length)); const auto end = from + length; - const auto activeFg = disabled ? _st.activeFgDisabled : anim::brush(_st.activeFg, _st.activeFgOver, over); + const auto activeFg = disabled + ? _st.activeFgDisabled + : _activeFgOverride + ? QBrush(*_activeFgOverride) + : anim::brush(_st.activeFg, _st.activeFgOver, over); const auto receivedTillFg = _st.receivedTillFg; - const auto inactiveFg = disabled ? _st.inactiveFgDisabled : anim::brush(_st.inactiveFg, _st.inactiveFgOver, over); + const auto inactiveFg = disabled + ? _st.inactiveFgDisabled + : anim::brush(_st.inactiveFg, _st.inactiveFgOver, over); if (mid > from) { - const auto fromClipRect = horizontal ? QRect(0, 0, mid, height()) : QRect(0, 0, width(), mid); + const auto fromClipRect = horizontal + ? QRect(0, 0, mid, height()) + : QRect(0, 0, width(), mid); const auto till = std::min(mid + radius, end); const auto fromRect = horizontal ? QRect(from, (height() - _st.width) / 2, till - from, _st.width) @@ -274,17 +289,31 @@ void MediaSlider::paintEvent(QPaintEvent *e) { auto clipRect = QRect(mid, 0, till - mid, height()); const auto left = std::max(mid - radius, from); const auto right = std::min(till + radius, end); - const auto rect = QRect(left, (height() - _st.width) / 2, right - left, _st.width); + const auto rect = QRect( + left, + (height() - _st.width) / 2, + right - left, + _st.width); p.setClipRect(clipRect); p.setBrush(receivedTillFg); p.drawRoundedRect(rect, radius, radius); } if (end > till) { - const auto endClipRect = horizontal ? QRect(till, 0, width() - till, height()) : QRect(0, till, width(), height() - till); + const auto endClipRect = horizontal + ? QRect(till, 0, width() - till, height()) + : QRect(0, till, width(), height() - till); const auto begin = std::max(till - radius, from); const auto endRect = horizontal - ? QRect(begin, (height() - _st.width) / 2, end - begin, _st.width) - : QRect((width() - _st.width) / 2, begin, _st.width, end - begin); + ? QRect( + begin, + (height() - _st.width) / 2, + end - begin, + _st.width) + : QRect( + (width() - _st.width) / 2, + begin, + _st.width, + end - begin); p.setClipRect(endClipRect); p.setBrush(horizontal ? inactiveFg : activeFg); p.drawRoundedRect(endRect, radius, radius); @@ -316,24 +345,46 @@ void MediaSlider::paintEvent(QPaintEvent *e) { p.drawRoundedRect(rect, dividerRadius, dividerRadius); } } - const auto markerSizeRatio = disabled ? 0. : (_alwaysDisplayMarker ? 1. : over); + const auto markerSizeRatio = disabled + ? 0. + : (_alwaysDisplayMarker ? 1. : over); if (markerSizeRatio > 0) { - const auto position = qRound(markerFrom + value * markerLength) - (horizontal ? (_st.seekSize.width() / 2) : (_st.seekSize.height() / 2)); + const auto position = qRound(markerFrom + value * markerLength) + - (horizontal + ? (_st.seekSize.width() / 2) + : (_st.seekSize.height() / 2)); const auto seekButton = horizontal - ? QRect(position, (height() - _st.seekSize.height()) / 2, _st.seekSize.width(), _st.seekSize.height()) - : QRect((width() - _st.seekSize.width()) / 2, position, _st.seekSize.width(), _st.seekSize.height()); - const auto size = horizontal ? _st.seekSize.width() : _st.seekSize.height(); - const auto remove = static_cast(((1. - markerSizeRatio) * size) / 2.); + ? QRect( + position, + (height() - _st.seekSize.height()) / 2, + _st.seekSize.width(), + _st.seekSize.height()) + : QRect( + (width() - _st.seekSize.width()) / 2, + position, + _st.seekSize.width(), + _st.seekSize.height()); + const auto size = horizontal + ? _st.seekSize.width() + : _st.seekSize.height(); + const auto remove = static_cast( + ((1. - markerSizeRatio) * size) / 2.); if (remove * 2 < size) { p.setClipRect(rect()); p.setBrush(activeFg); const auto xshift = horizontal - ? std::max(seekButton.x() + seekButton.width() - remove - width(), 0) + std::min(seekButton.x() + remove, 0) + ? std::max( + seekButton.x() + seekButton.width() - remove - width(), + 0) + std::min(seekButton.x() + remove, 0) : 0; const auto yshift = horizontal ? 0 - : std::max(seekButton.y() + seekButton.height() - remove - height(), 0) + std::min(seekButton.y() + remove, 0); - p.drawEllipse(seekButton.marginsRemoved(QMargins(remove, remove, remove, remove)).translated(-xshift, -yshift)); + : std::max( + seekButton.y() + seekButton.height() - remove - height(), + 0) + std::min(seekButton.y() + remove, 0); + p.drawEllipse(seekButton.marginsRemoved( + QMargins(remove, remove, remove, remove) + ).translated(-xshift, -yshift)); } } } diff --git a/Telegram/SourceFiles/ui/widgets/continuous_sliders.h b/Telegram/SourceFiles/ui/widgets/continuous_sliders.h index 1141b8fa9..44d775dbd 100644 --- a/Telegram/SourceFiles/ui/widgets/continuous_sliders.h +++ b/Telegram/SourceFiles/ui/widgets/continuous_sliders.h @@ -178,7 +178,7 @@ public: callback(convert(index)); }); } - + void setActiveFgOverride(std::optional color); void addDivider(float64 atValue, const QSize &size); protected: @@ -198,6 +198,7 @@ private: bool _paintDisabled = false; std::vector _dividers; + std::optional _activeFgOverride; };