Improve volume slider design in group calls.

This commit is contained in:
John Preston 2021-06-17 16:22:51 +04:00
parent 55e494f55a
commit ec234cdc43
6 changed files with 139 additions and 65 deletions

View file

@ -490,6 +490,7 @@ groupCallMenu: Menu(defaultMenu) {
itemFgShortcutDisabled: groupCallMemberNotJoinedStatus; itemFgShortcutDisabled: groupCallMemberNotJoinedStatus;
separatorFg: groupCallMenuBgOver; separatorFg: groupCallMenuBgOver;
separatorPadding: margins(0px, 4px, 0px, 4px);
arrow: icon {{ "dropdown_submenu_arrow", groupCallMemberNotJoinedStatus }}; arrow: icon {{ "dropdown_submenu_arrow", groupCallMemberNotJoinedStatus }};
@ -513,6 +514,12 @@ groupCallPopupMenu: PopupMenu(defaultPopupMenu) {
menu: groupCallMenu; menu: groupCallMenu;
animation: groupCallPanelAnimation; animation: groupCallPanelAnimation;
} }
groupCallPopupMenuWithVolume: PopupMenu(groupCallPopupMenu) {
scrollPadding: margins(0px, 3px, 0px, 8px);
menu: Menu(groupCallMenu) {
widthMin: 210px;
}
}
groupCallRecordingTimerPadding: margins(0px, 4px, 0px, 4px); groupCallRecordingTimerPadding: margins(0px, 4px, 0px, 4px);
groupCallRecordingTimerFont: font(12px); groupCallRecordingTimerFont: font(12px);
@ -1071,6 +1078,8 @@ groupCallMuteCrossLine: CrossLineAnimation {
groupCallMenuSpeakerArcsSkip: 1px; groupCallMenuSpeakerArcsSkip: 1px;
groupCallMenuVolumeSkip: 5px; groupCallMenuVolumeSkip: 5px;
groupCallMenuVolumePadding: margins(17px, 6px, 17px, 5px);
groupCallMenuVolumeMargin: margins(55px, 0px, 15px, 0px);
groupCallMenuVolumeSlider: MediaSlider(defaultContinuousSlider) { groupCallMenuVolumeSlider: MediaSlider(defaultContinuousSlider) {
activeFg: groupCallMembersFg; activeFg: groupCallMembersFg;
inactiveFg: groupCallMemberInactiveIcon; inactiveFg: groupCallMemberInactiveIcon;
@ -1078,6 +1087,8 @@ groupCallMenuVolumeSlider: MediaSlider(defaultContinuousSlider) {
inactiveFgOver: groupCallMemberInactiveIcon; inactiveFgOver: groupCallMemberInactiveIcon;
activeFgDisabled: groupCallMemberInactiveIcon; activeFgDisabled: groupCallMemberInactiveIcon;
receivedTillFg: groupCallMemberInactiveIcon; receivedTillFg: groupCallMemberInactiveIcon;
width: 7px;
seekSize: size(7px, 7px);
} }
groupCallSpeakerArcsAnimation: ArcsAnimation { groupCallSpeakerArcsAnimation: ArcsAnimation {

View file

@ -1236,12 +1236,10 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
not_null<PeerListRow*> row) { not_null<PeerListRow*> row) {
const auto participantPeer = row->peer(); const auto participantPeer = row->peer();
const auto real = static_cast<Row*>(row.get()); const auto real = static_cast<Row*>(row.get());
auto result = base::make_unique_q<Ui::PopupMenu>(
parent,
st::groupCallPopupMenu);
const auto muteState = real->state(); 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 admin = IsGroupCallAdmin(_peer, participantPeer);
const auto session = &_peer->session(); const auto session = &_peer->session();
const auto getCurrentWindow = [=]() -> Window::SessionController* { const auto getCurrentWindow = [=]() -> Window::SessionController* {
@ -1262,6 +1260,12 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
} }
return getCurrentWindow(); return getCurrentWindow();
}; };
auto result = base::make_unique_q<Ui::PopupMenu>(
parent,
(addVolumeItem
? st::groupCallPopupMenuWithVolume
: st::groupCallPopupMenu));
const auto weakMenu = Ui::MakeWeak(result.get()); const auto weakMenu = Ui::MakeWeak(result.get());
const auto performOnMainWindow = [=](auto callback) { const auto performOnMainWindow = [=](auto callback) {
if (const auto window = getWindow()) { if (const auto window = getWindow()) {
@ -1442,7 +1446,8 @@ void Members::Controller::addMuteActionsToContextMenu(
auto mutesFromVolume = rpl::never<bool>() | rpl::type_erased(); auto mutesFromVolume = rpl::never<bool>() | rpl::type_erased();
if (!muted || _call->joinAs() == participantPeer) { const auto addVolumeItem = !muted || isMe(participantPeer);
if (addVolumeItem) {
auto otherParticipantStateValue auto otherParticipantStateValue
= _call->otherParticipantStateValue( = _call->otherParticipantStateValue(
) | rpl::filter([=](const Group::ParticipantState &data) { ) | rpl::filter([=](const Group::ParticipantState &data) {
@ -1451,7 +1456,7 @@ void Members::Controller::addMuteActionsToContextMenu(
auto volumeItem = base::make_unique_q<MenuVolumeItem>( auto volumeItem = base::make_unique_q<MenuVolumeItem>(
menu->menu(), menu->menu(),
st::groupCallPopupMenu.menu, st::groupCallPopupMenuWithVolume.menu,
otherParticipantStateValue, otherParticipantStateValue,
row->volume(), row->volume(),
Group::kMaxVolume, Group::kMaxVolume,
@ -1491,6 +1496,10 @@ void Members::Controller::addMuteActionsToContextMenu(
}, volumeItem->lifetime()); }, volumeItem->lifetime());
menu->addAction(std::move(volumeItem)); menu->addAction(std::move(volumeItem));
if (!isMe(participantPeer)) {
menu->addSeparator();
}
}; };
const auto muteAction = [&]() -> QAction* { const auto muteAction = [&]() -> QAction* {

View file

@ -36,10 +36,6 @@ constexpr auto kVolumeStickedValues =
{ 175. / kMaxVolumePercent, 2. / kMaxVolumePercent }, { 175. / kMaxVolumePercent, 2. / kMaxVolumePercent },
}}; }};
QString VolumeString(int volumePercent) {
return u"%1%"_q.arg(volumePercent);
}
} // namespace } // namespace
MenuVolumeItem::MenuVolumeItem( MenuVolumeItem::MenuVolumeItem(
@ -75,20 +71,21 @@ MenuVolumeItem::MenuVolumeItem(
sizeValue( sizeValue(
) | rpl::start_with_next([=](const QSize &size) { ) | rpl::start_with_next([=](const QSize &size) {
const auto geometry = QRect(QPoint(), size); const auto geometry = QRect(QPoint(), size);
_itemRect = geometry - _st.itemPadding; _itemRect = geometry - st::groupCallMenuVolumePadding;
_speakerRect = QRect(_itemRect.topLeft(), _stCross.icon.size()); _speakerRect = QRect(_itemRect.topLeft(), _stCross.icon.size());
_arcPosition = _speakerRect.center() _arcPosition = _speakerRect.center()
+ QPoint(0, st::groupCallMenuSpeakerArcsSkip); + QPoint(0, st::groupCallMenuSpeakerArcsSkip);
_volumeRect = QRect( const auto sliderLeft = _arcPosition.x()
_arcPosition.x() + st::groupCallMenuVolumeSkip
+ st::groupCallMenuVolumeSkip + _arcs->maxWidth()
+ _arcs->finishedWidth(), + st::groupCallMenuVolumeSkip;
_slider->setGeometry(
st::groupCallMenuVolumeMargin.left(),
_speakerRect.y(), _speakerRect.y(),
_st.itemStyle.font->width(VolumeString(kMaxVolumePercent)), (geometry.width()
- st::groupCallMenuVolumeMargin.left()
- st::groupCallMenuVolumeMargin.right()),
_speakerRect.height()); _speakerRect.height());
_slider->setGeometry(_itemRect
- style::margins(0, contentHeight() / 2, 0, 0));
}, lifetime()); }, lifetime());
setCloudVolume(startVolume); setCloudVolume(startVolume);
@ -110,15 +107,12 @@ MenuVolumeItem::MenuVolumeItem(
unmuteColor(), unmuteColor(),
muteColor(), muteColor(),
muteProgress); muteProgress);
p.setPen(mutePen);
p.setFont(_st.itemStyle.font);
p.drawText(_volumeRect, VolumeString(volume), style::al_left);
_crossLineMute->paint( _crossLineMute->paint(
p, p,
_speakerRect.topLeft(), _speakerRect.topLeft(),
muteProgress, muteProgress,
(!muteProgress) ? std::nullopt : std::optional<QColor>(mutePen)); (muteProgress > 0) ? std::make_optional(mutePen) : std::nullopt);
{ {
p.translate(_arcPosition); p.translate(_arcPosition);
@ -133,7 +127,7 @@ MenuVolumeItem::MenuVolumeItem(
_toggleMuteLocallyRequests.fire_copy(newMuted); _toggleMuteLocallyRequests.fire_copy(newMuted);
_crossLineAnimation.start( _crossLineAnimation.start(
[=] { update(_speakerRect.united(_volumeRect)); }, [=] { update(_speakerRect); },
_localMuted ? 0. : 1., _localMuted ? 0. : 1.,
_localMuted ? 1. : 0., _localMuted ? 1. : 0.,
st::callPanelDuration); st::callPanelDuration);
@ -141,8 +135,8 @@ MenuVolumeItem::MenuVolumeItem(
if (value > 0) { if (value > 0) {
_changeVolumeLocallyRequests.fire(value * _maxVolume); _changeVolumeLocallyRequests.fire(value * _maxVolume);
} }
update(_volumeRect);
_arcs->setValue(value); _arcs->setValue(value);
updateSliderColor(value);
}); });
const auto returnVolume = [=] { const auto returnVolume = [=] {
@ -169,6 +163,7 @@ MenuVolumeItem::MenuVolumeItem(
if (!_cloudMuted && !muted) { if (!_cloudMuted && !muted) {
_changeVolumeRequests.fire_copy(newVolume); _changeVolumeRequests.fire_copy(newVolume);
} }
updateSliderColor(value);
}); });
std::move( std::move(
@ -209,30 +204,15 @@ MenuVolumeItem::MenuVolumeItem(
} }
void MenuVolumeItem::initArcsAnimation() { void MenuVolumeItem::initArcsAnimation() {
const auto volumeLeftWas = lifetime().make_state<int>(0);
const auto lastTime = lifetime().make_state<int>(0); const auto lastTime = lifetime().make_state<int>(0);
_arcsAnimation.init([=](crl::time now) { _arcsAnimation.init([=](crl::time now) {
_arcs->update(now); _arcs->update(now);
update(_speakerRect); 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( _arcs->startUpdateRequests(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
if (!_arcsAnimation.animating()) { if (!_arcsAnimation.animating()) {
*volumeLeftWas = _volumeRect.left();
*lastTime = crl::now(); *lastTime = crl::now();
_arcsAnimation.start(); _arcsAnimation.start();
} }
@ -269,8 +249,30 @@ void MenuVolumeItem::setCloudVolume(int volume) {
} }
void MenuVolumeItem::setSliderVolume(int volume) { void MenuVolumeItem::setSliderVolume(int volume) {
_slider->setValue(float64(volume) / _maxVolume); const auto value = float64(volume) / _maxVolume;
update(_volumeRect); _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<QColor, 4>{ {
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<QAction*> MenuVolumeItem::action() const { not_null<QAction*> MenuVolumeItem::action() const {
@ -282,9 +284,9 @@ bool MenuVolumeItem::isEnabled() const {
} }
int MenuVolumeItem::contentHeight() const { int MenuVolumeItem::contentHeight() const {
return _st.itemPadding.top() return st::groupCallMenuVolumePadding.top()
+ _st.itemPadding.bottom() + st::groupCallMenuVolumePadding.bottom()
+ _stCross.icon.height() * 2; + _stCross.icon.height();
} }
rpl::producer<bool> MenuVolumeItem::toggleMuteRequests() const { rpl::producer<bool> MenuVolumeItem::toggleMuteRequests() const {

View file

@ -52,6 +52,7 @@ private:
void setCloudVolume(int volume); void setCloudVolume(int volume);
void setSliderVolume(int volume); void setSliderVolume(int volume);
void updateSliderColor(float64 value);
QColor unmuteColor() const; QColor unmuteColor() const;
QColor muteColor() const; QColor muteColor() const;
@ -64,7 +65,6 @@ private:
QRect _itemRect; QRect _itemRect;
QRect _speakerRect; QRect _speakerRect;
QRect _volumeRect;
QPoint _arcPosition; QPoint _arcPosition;
const base::unique_qptr<Ui::MediaSlider> _slider; const base::unique_qptr<Ui::MediaSlider> _slider;

View file

@ -223,6 +223,11 @@ void MediaSlider::addDivider(float64 atValue, const QSize &size) {
_dividers.push_back(Divider{ atValue, size }); _dividers.push_back(Divider{ atValue, size });
} }
void MediaSlider::setActiveFgOverride(std::optional<QColor> color) {
_activeFgOverride = color;
update();
}
void MediaSlider::paintEvent(QPaintEvent *e) { void MediaSlider::paintEvent(QPaintEvent *e) {
if (_paintDisabled) { if (_paintDisabled) {
return; return;
@ -250,17 +255,27 @@ void MediaSlider::paintEvent(QPaintEvent *e) {
: value; : value;
const auto markerFrom = (horizontal ? seekRect.x() : seekRect.y()); 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 from = 0;
const auto length = (horizontal ? width() : height()); const auto length = (horizontal ? width() : height());
const auto mid = qRound(from + value * length); const auto mid = qRound(from + value * length);
const auto till = std::max(mid, qRound(from + receivedTill * length)); const auto till = std::max(mid, qRound(from + receivedTill * length));
const auto end = from + 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 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) { 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 till = std::min(mid + radius, end);
const auto fromRect = horizontal const auto fromRect = horizontal
? QRect(from, (height() - _st.width) / 2, till - from, _st.width) ? 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()); auto clipRect = QRect(mid, 0, till - mid, height());
const auto left = std::max(mid - radius, from); const auto left = std::max(mid - radius, from);
const auto right = std::min(till + radius, end); 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.setClipRect(clipRect);
p.setBrush(receivedTillFg); p.setBrush(receivedTillFg);
p.drawRoundedRect(rect, radius, radius); p.drawRoundedRect(rect, radius, radius);
} }
if (end > till) { 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 begin = std::max(till - radius, from);
const auto endRect = horizontal const auto endRect = horizontal
? QRect(begin, (height() - _st.width) / 2, end - begin, _st.width) ? QRect(
: QRect((width() - _st.width) / 2, begin, _st.width, end - begin); begin,
(height() - _st.width) / 2,
end - begin,
_st.width)
: QRect(
(width() - _st.width) / 2,
begin,
_st.width,
end - begin);
p.setClipRect(endClipRect); p.setClipRect(endClipRect);
p.setBrush(horizontal ? inactiveFg : activeFg); p.setBrush(horizontal ? inactiveFg : activeFg);
p.drawRoundedRect(endRect, radius, radius); p.drawRoundedRect(endRect, radius, radius);
@ -316,24 +345,46 @@ void MediaSlider::paintEvent(QPaintEvent *e) {
p.drawRoundedRect(rect, dividerRadius, dividerRadius); p.drawRoundedRect(rect, dividerRadius, dividerRadius);
} }
} }
const auto markerSizeRatio = disabled ? 0. : (_alwaysDisplayMarker ? 1. : over); const auto markerSizeRatio = disabled
? 0.
: (_alwaysDisplayMarker ? 1. : over);
if (markerSizeRatio > 0) { 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 const auto seekButton = horizontal
? QRect(position, (height() - _st.seekSize.height()) / 2, _st.seekSize.width(), _st.seekSize.height()) ? QRect(
: QRect((width() - _st.seekSize.width()) / 2, position, _st.seekSize.width(), _st.seekSize.height()); position,
const auto size = horizontal ? _st.seekSize.width() : _st.seekSize.height(); (height() - _st.seekSize.height()) / 2,
const auto remove = static_cast<int>(((1. - markerSizeRatio) * size) / 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<int>(
((1. - markerSizeRatio) * size) / 2.);
if (remove * 2 < size) { if (remove * 2 < size) {
p.setClipRect(rect()); p.setClipRect(rect());
p.setBrush(activeFg); p.setBrush(activeFg);
const auto xshift = horizontal 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; : 0;
const auto yshift = horizontal const auto yshift = horizontal
? 0 ? 0
: std::max(seekButton.y() + seekButton.height() - remove - height(), 0) + std::min(seekButton.y() + remove, 0); : std::max(
p.drawEllipse(seekButton.marginsRemoved(QMargins(remove, remove, remove, remove)).translated(-xshift, -yshift)); 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));
} }
} }
} }

View file

@ -178,7 +178,7 @@ public:
callback(convert(index)); callback(convert(index));
}); });
} }
void setActiveFgOverride(std::optional<QColor> color);
void addDivider(float64 atValue, const QSize &size); void addDivider(float64 atValue, const QSize &size);
protected: protected:
@ -198,6 +198,7 @@ private:
bool _paintDisabled = false; bool _paintDisabled = false;
std::vector<Divider> _dividers; std::vector<Divider> _dividers;
std::optional<QColor> _activeFgOverride;
}; };