mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-23 08:46:55 +00:00
Improve volume slider design in group calls.
This commit is contained in:
parent
55e494f55a
commit
ec234cdc43
@ -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 {
|
||||
|
@ -1236,12 +1236,10 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||
not_null<PeerListRow*> row) {
|
||||
const auto participantPeer = row->peer();
|
||||
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 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<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||
}
|
||||
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 performOnMainWindow = [=](auto callback) {
|
||||
if (const auto window = getWindow()) {
|
||||
@ -1442,7 +1446,8 @@ void Members::Controller::addMuteActionsToContextMenu(
|
||||
|
||||
auto mutesFromVolume = rpl::never<bool>() | 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<MenuVolumeItem>(
|
||||
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* {
|
||||
|
@ -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<QColor>(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<int>(0);
|
||||
const auto lastTime = lifetime().make_state<int>(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<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 {
|
||||
@ -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<bool> MenuVolumeItem::toggleMuteRequests() const {
|
||||
|
@ -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<Ui::MediaSlider> _slider;
|
||||
|
@ -223,6 +223,11 @@ void MediaSlider::addDivider(float64 atValue, const QSize &size) {
|
||||
_dividers.push_back(Divider{ atValue, size });
|
||||
}
|
||||
|
||||
void MediaSlider::setActiveFgOverride(std::optional<QColor> 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<int>(((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<int>(
|
||||
((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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -178,7 +178,7 @@ public:
|
||||
callback(convert(index));
|
||||
});
|
||||
}
|
||||
|
||||
void setActiveFgOverride(std::optional<QColor> color);
|
||||
void addDivider(float64 atValue, const QSize &size);
|
||||
|
||||
protected:
|
||||
@ -198,6 +198,7 @@ private:
|
||||
bool _paintDisabled = false;
|
||||
|
||||
std::vector<Divider> _dividers;
|
||||
std::optional<QColor> _activeFgOverride;
|
||||
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user