init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
301
Telegram/SourceFiles/calls/group/calls_volume_item.cpp
Normal file
301
Telegram/SourceFiles/calls/group/calls_volume_item.cpp
Normal file
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "calls/group/calls_volume_item.h"
|
||||
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "ui/color_int_conversion.h"
|
||||
#include "ui/effects/animation_value.h"
|
||||
#include "ui/effects/cross_line.h"
|
||||
#include "ui/widgets/continuous_sliders.h"
|
||||
#include "ui/rect.h"
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
#include "ui/paint/arcs.h"
|
||||
|
||||
namespace Calls {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxVolumePercent = 200;
|
||||
|
||||
const auto kSpeakerThreshold = std::vector<float>{
|
||||
10.0f / kMaxVolumePercent,
|
||||
50.0f / kMaxVolumePercent,
|
||||
150.0f / kMaxVolumePercent };
|
||||
|
||||
constexpr auto kVolumeStickedValues
|
||||
= std::array<std::pair<float64, float64>, 7>{{
|
||||
{ 25. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
{ 50. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
{ 75. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
{ 100. / kMaxVolumePercent, 10. / kMaxVolumePercent },
|
||||
{ 125. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
{ 150. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
{ 175. / kMaxVolumePercent, 2. / kMaxVolumePercent },
|
||||
}};
|
||||
|
||||
} // namespace
|
||||
|
||||
MenuVolumeItem::MenuVolumeItem(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
const style::MediaSlider &stSlider,
|
||||
rpl::producer<Group::ParticipantState> participantState,
|
||||
int startVolume,
|
||||
int maxVolume,
|
||||
bool muted,
|
||||
const QMargins &padding)
|
||||
: Ui::Menu::ItemBase(parent, st)
|
||||
, _maxVolume(maxVolume)
|
||||
, _cloudMuted(muted)
|
||||
, _localMuted(muted)
|
||||
, _slider(base::make_unique_q<Ui::MediaSlider>(this, stSlider))
|
||||
, _dummyAction(new QAction(parent))
|
||||
, _st(st)
|
||||
, _stCross(st::groupCallMuteCrossLine)
|
||||
, _padding(padding)
|
||||
, _crossLineMute(std::make_unique<Ui::CrossLineAnimation>(_stCross, true))
|
||||
, _arcs(std::make_unique<Ui::Paint::ArcsAnimation>(
|
||||
st::groupCallSpeakerArcsAnimation,
|
||||
kSpeakerThreshold,
|
||||
_localMuted ? 0. : (startVolume / float(maxVolume)),
|
||||
Ui::Paint::ArcsAnimation::Direction::Right)) {
|
||||
|
||||
initResizeHook(parent->sizeValue());
|
||||
enableMouseSelecting();
|
||||
enableMouseSelecting(_slider.get());
|
||||
|
||||
_slider->setAlwaysDisplayMarker(true);
|
||||
|
||||
sizeValue(
|
||||
) | rpl::on_next([=](const QSize &size) {
|
||||
const auto geometry = QRect(QPoint(), size);
|
||||
_itemRect = geometry - _padding;
|
||||
_speakerRect = QRect(_itemRect.topLeft(), _stCross.icon.size());
|
||||
_arcPosition = _speakerRect.center()
|
||||
+ QPoint(0, st::groupCallMenuSpeakerArcsSkip);
|
||||
_slider->setGeometry(
|
||||
st::groupCallMenuVolumeMargin.left(),
|
||||
_speakerRect.y(),
|
||||
(geometry.width()
|
||||
- st::groupCallMenuVolumeMargin.left()
|
||||
- st::groupCallMenuVolumeMargin.right()),
|
||||
_speakerRect.height());
|
||||
}, lifetime());
|
||||
|
||||
setCloudVolume(startVolume);
|
||||
|
||||
paintRequest(
|
||||
) | rpl::on_next([=](const QRect &clip) {
|
||||
auto p = QPainter(this);
|
||||
|
||||
const auto volume = _localMuted
|
||||
? 0
|
||||
: base::SafeRound(_slider->value() * kMaxVolumePercent);
|
||||
const auto muteProgress
|
||||
= _crossLineAnimation.value((!volume) ? 1. : 0.);
|
||||
|
||||
const auto selected = isSelected();
|
||||
p.fillRect(clip, selected ? st.itemBgOver : st.itemBg);
|
||||
|
||||
const auto mutePen = anim::color(
|
||||
unmuteColor(),
|
||||
muteColor(),
|
||||
muteProgress);
|
||||
|
||||
_crossLineMute->paint(
|
||||
p,
|
||||
_speakerRect.topLeft(),
|
||||
muteProgress,
|
||||
(muteProgress > 0) ? std::make_optional(mutePen) : std::nullopt);
|
||||
|
||||
{
|
||||
p.translate(_arcPosition);
|
||||
_arcs->paint(p);
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
_slider->setChangeProgressCallback([=](float64 value) {
|
||||
const auto newMuted = (value == 0);
|
||||
if (_localMuted != newMuted) {
|
||||
_localMuted = newMuted;
|
||||
_toggleMuteLocallyRequests.fire_copy(newMuted);
|
||||
|
||||
_crossLineAnimation.start(
|
||||
[=] { update(_speakerRect); },
|
||||
_localMuted ? 0. : 1.,
|
||||
_localMuted ? 1. : 0.,
|
||||
st::callPanelDuration);
|
||||
}
|
||||
if (value > 0) {
|
||||
_changeVolumeLocallyRequests.fire(value * _maxVolume);
|
||||
}
|
||||
_arcs->setValue(value);
|
||||
updateSliderColor(value);
|
||||
});
|
||||
|
||||
const auto returnVolume = [=] {
|
||||
_changeVolumeLocallyRequests.fire_copy(_cloudVolume);
|
||||
};
|
||||
|
||||
_slider->setChangeFinishedCallback([=](float64 value) {
|
||||
const auto newVolume = base::SafeRound(value * _maxVolume);
|
||||
const auto muted = (value == 0);
|
||||
|
||||
if (!_cloudMuted && muted) {
|
||||
returnVolume();
|
||||
_localMuted = true;
|
||||
_toggleMuteRequests.fire(true);
|
||||
}
|
||||
if (_cloudMuted && muted) {
|
||||
returnVolume();
|
||||
}
|
||||
if (_cloudMuted && !muted) {
|
||||
_waitingForUpdateVolume = true;
|
||||
_localMuted = false;
|
||||
_toggleMuteRequests.fire(false);
|
||||
}
|
||||
if (!_cloudMuted && !muted) {
|
||||
_changeVolumeRequests.fire_copy(newVolume);
|
||||
}
|
||||
updateSliderColor(value);
|
||||
});
|
||||
|
||||
std::move(
|
||||
participantState
|
||||
) | rpl::on_next([=](const Group::ParticipantState &state) {
|
||||
const auto newMuted = state.mutedByMe;
|
||||
const auto newVolume = state.volume.value_or(0);
|
||||
|
||||
_cloudMuted = _localMuted = newMuted;
|
||||
|
||||
if (!newVolume) {
|
||||
return;
|
||||
}
|
||||
if (_waitingForUpdateVolume) {
|
||||
const auto localVolume
|
||||
= base::SafeRound(_slider->value() * _maxVolume);
|
||||
if ((localVolume != newVolume)
|
||||
&& (_cloudVolume == newVolume)) {
|
||||
_changeVolumeRequests.fire(int(localVolume));
|
||||
}
|
||||
} else {
|
||||
setCloudVolume(newVolume);
|
||||
}
|
||||
_waitingForUpdateVolume = false;
|
||||
}, lifetime());
|
||||
|
||||
_slider->setAdjustCallback([=](float64 value) {
|
||||
for (const auto &snap : kVolumeStickedValues) {
|
||||
if (value > (snap.first - snap.second)
|
||||
&& value < (snap.first + snap.second)) {
|
||||
return snap.first;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
initArcsAnimation();
|
||||
}
|
||||
|
||||
void MenuVolumeItem::initArcsAnimation() {
|
||||
const auto lastTime = lifetime().make_state<int>(0);
|
||||
_arcsAnimation.init([=](crl::time now) {
|
||||
_arcs->update(now);
|
||||
update(_speakerRect);
|
||||
});
|
||||
|
||||
_arcs->startUpdateRequests(
|
||||
) | rpl::on_next([=] {
|
||||
if (!_arcsAnimation.animating()) {
|
||||
*lastTime = crl::now();
|
||||
_arcsAnimation.start();
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
_arcs->stopUpdateRequests(
|
||||
) | rpl::on_next([=] {
|
||||
_arcsAnimation.stop();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
QColor MenuVolumeItem::unmuteColor() const {
|
||||
return (isSelected()
|
||||
? _st.itemFgOver
|
||||
: isEnabled()
|
||||
? _st.itemFg
|
||||
: _st.itemFgDisabled)->c;
|
||||
}
|
||||
|
||||
QColor MenuVolumeItem::muteColor() const {
|
||||
return (isSelected()
|
||||
? st::attentionButtonFgOver
|
||||
: st::attentionButtonFg)->c;
|
||||
}
|
||||
|
||||
void MenuVolumeItem::setCloudVolume(int volume) {
|
||||
if (_cloudVolume == volume) {
|
||||
return;
|
||||
}
|
||||
_cloudVolume = volume;
|
||||
if (!_slider->isChanging()) {
|
||||
setSliderVolume(_cloudMuted ? 0. : volume);
|
||||
}
|
||||
}
|
||||
|
||||
void MenuVolumeItem::setSliderVolume(int volume) {
|
||||
const auto value = float64(volume) / _maxVolume;
|
||||
_slider->setValue(value);
|
||||
updateSliderColor(value);
|
||||
}
|
||||
|
||||
void MenuVolumeItem::updateSliderColor(float64 value) {
|
||||
value = std::clamp(value, 0., 1.);
|
||||
const auto colors = std::array<QColor, 4>{ {
|
||||
Ui::ColorFromSerialized(0xF66464),
|
||||
Ui::ColorFromSerialized(0xD0B738),
|
||||
Ui::ColorFromSerialized(0x24CD80),
|
||||
Ui::ColorFromSerialized(0x3BBCEC),
|
||||
} };
|
||||
_slider->setColorOverrides({
|
||||
.activeFg = (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 {
|
||||
return _dummyAction;
|
||||
}
|
||||
|
||||
bool MenuVolumeItem::isEnabled() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
int MenuVolumeItem::contentHeight() const {
|
||||
return rect::m::sum::v(_padding) + _stCross.icon.height();
|
||||
}
|
||||
|
||||
rpl::producer<bool> MenuVolumeItem::toggleMuteRequests() const {
|
||||
return _toggleMuteRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<bool> MenuVolumeItem::toggleMuteLocallyRequests() const {
|
||||
return _toggleMuteLocallyRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<int> MenuVolumeItem::changeVolumeRequests() const {
|
||||
return _changeVolumeRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<int> MenuVolumeItem::changeVolumeLocallyRequests() const {
|
||||
return _changeVolumeLocallyRequests.events();
|
||||
}
|
||||
|
||||
} // namespace Calls
|
||||
Reference in New Issue
Block a user