304 lines
		
	
	
	
		
			7.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			304 lines
		
	
	
	
		
			7.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
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/effects/animation_value.h"
 | 
						|
#include "ui/effects/cross_line.h"
 | 
						|
#include "ui/widgets/continuous_sliders.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,
 | 
						|
	rpl::producer<Group::ParticipantState> participantState,
 | 
						|
	int startVolume,
 | 
						|
	int maxVolume,
 | 
						|
	bool muted)
 | 
						|
: Ui::Menu::ItemBase(parent, st)
 | 
						|
, _maxVolume(maxVolume)
 | 
						|
, _cloudMuted(muted)
 | 
						|
, _localMuted(muted)
 | 
						|
, _slider(base::make_unique_q<Ui::MediaSlider>(
 | 
						|
	this,
 | 
						|
	st::groupCallMenuVolumeSlider))
 | 
						|
, _dummyAction(new QAction(parent))
 | 
						|
, _st(st)
 | 
						|
, _stCross(st::groupCallMuteCrossLine)
 | 
						|
, _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::start_with_next([=](const QSize &size) {
 | 
						|
		const auto geometry = QRect(QPoint(), size);
 | 
						|
		_itemRect = geometry - st::groupCallMenuVolumePadding;
 | 
						|
		_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::start_with_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::start_with_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::start_with_next([=] {
 | 
						|
		if (!_arcsAnimation.animating()) {
 | 
						|
			*lastTime = crl::now();
 | 
						|
			_arcsAnimation.start();
 | 
						|
		}
 | 
						|
	}, lifetime());
 | 
						|
 | 
						|
	_arcs->stopUpdateRequests(
 | 
						|
	) | rpl::start_with_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 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 {
 | 
						|
	return _dummyAction;
 | 
						|
}
 | 
						|
 | 
						|
bool MenuVolumeItem::isEnabled() const {
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
int MenuVolumeItem::contentHeight() const {
 | 
						|
	return st::groupCallMenuVolumePadding.top()
 | 
						|
		+ st::groupCallMenuVolumePadding.bottom()
 | 
						|
		+ _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
 |