1174 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1174 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // This file is part of Desktop App Toolkit,
 | |
| // a set of libraries for developing nice desktop applications.
 | |
| //
 | |
| // For license and copyright information please follow this link:
 | |
| // https://github.com/desktop-app/legal/blob/master/LEGAL
 | |
| //
 | |
| #include "ui/controls/call_mute_button.h"
 | |
| 
 | |
| #include "base/flat_map.h"
 | |
| #include "ui/abstract_button.h"
 | |
| #include "ui/paint/blobs.h"
 | |
| #include "ui/painter.h"
 | |
| #include "ui/widgets/call_button.h"
 | |
| #include "ui/widgets/labels.h"
 | |
| #include "base/random.h"
 | |
| #include "styles/palette.h"
 | |
| #include "styles/style_widgets.h"
 | |
| #include "styles/style_calls.h"
 | |
| 
 | |
| #include <QtCore/QtMath>
 | |
| #include <QtCore/QCoreApplication>
 | |
| 
 | |
| namespace Ui {
 | |
| namespace {
 | |
| 
 | |
| using Radiuses = Paint::Blob::Radiuses;
 | |
| 
 | |
| constexpr auto kMaxLevel = 1.;
 | |
| 
 | |
| constexpr auto kLevelDuration = 100. + 500. * 0.33;
 | |
| 
 | |
| constexpr auto kScaleBig = 0.807 - 0.1;
 | |
| constexpr auto kScaleSmall = 0.704 - 0.1;
 | |
| 
 | |
| constexpr auto kScaleBigMin = 0.878;
 | |
| constexpr auto kScaleSmallMin = 0.926;
 | |
| 
 | |
| constexpr auto kScaleBigMax = (float)(kScaleBigMin + kScaleBig);
 | |
| constexpr auto kScaleSmallMax = (float)(kScaleSmallMin + kScaleSmall);
 | |
| 
 | |
| constexpr auto kMainRadiusFactor = (float)(50. / 57.);
 | |
| 
 | |
| constexpr auto kGlowPaddingFactor = 1.2;
 | |
| constexpr auto kGlowMinScale = 0.6;
 | |
| constexpr auto kGlowAlpha = 150;
 | |
| 
 | |
| constexpr auto kOverrideColorBgAlpha = 76;
 | |
| constexpr auto kOverrideColorRippleAlpha = 50;
 | |
| 
 | |
| constexpr auto kShiftDuration = crl::time(300);
 | |
| constexpr auto kSwitchStateDuration = crl::time(120);
 | |
| constexpr auto kSwitchLabelDuration = crl::time(180);
 | |
| 
 | |
| // Switch state from Connecting animation.
 | |
| constexpr auto kSwitchRadialDuration = crl::time(350);
 | |
| constexpr auto kSwitchCirclelDuration = crl::time(275);
 | |
| constexpr auto kBlobsScaleEnterDuration = crl::time(400);
 | |
| constexpr auto kSwitchStateFromConnectingDuration = kSwitchRadialDuration
 | |
| 	+ kSwitchCirclelDuration
 | |
| 	+ kBlobsScaleEnterDuration;
 | |
| 
 | |
| constexpr auto kRadialEndPartAnimation = float(kSwitchRadialDuration)
 | |
| 	/ kSwitchStateFromConnectingDuration;
 | |
| constexpr auto kBlobsWidgetPartAnimation = 1. - kRadialEndPartAnimation;
 | |
| constexpr auto kFillCirclePartAnimation = float(kSwitchCirclelDuration)
 | |
| 	/ (kSwitchCirclelDuration + kBlobsScaleEnterDuration);
 | |
| constexpr auto kBlobPartAnimation = float(kBlobsScaleEnterDuration)
 | |
| 	/ (kSwitchCirclelDuration + kBlobsScaleEnterDuration);
 | |
| 
 | |
| constexpr auto kOverlapProgressRadialHide = 1.2;
 | |
| 
 | |
| constexpr auto kRadialFinishArcShift = 1200;
 | |
| 
 | |
| [[nodiscard]] CallMuteButtonType TypeForIcon(CallMuteButtonType type) {
 | |
| 	return (type == CallMuteButtonType::Connecting)
 | |
| 		? CallMuteButtonType::Muted
 | |
| 		: (type == CallMuteButtonType::RaisedHand)
 | |
| 		? CallMuteButtonType::ForceMuted
 | |
| 		: type;
 | |
| };
 | |
| 
 | |
| auto MuteBlobs() {
 | |
| 	return std::vector<Paint::Blobs::BlobData>{
 | |
| 		{
 | |
| 			.segmentsCount = 9,
 | |
| 			.minScale = kScaleSmallMin / kScaleSmallMax,
 | |
| 			.minRadius = st::callMuteMinorBlobMinRadius
 | |
| 				* kScaleSmallMax
 | |
| 				* kMainRadiusFactor,
 | |
| 			.maxRadius = st::callMuteMinorBlobMaxRadius
 | |
| 				* kScaleSmallMax
 | |
| 				* kMainRadiusFactor,
 | |
| 			.speedScale = 1.,
 | |
| 			.alpha = (76. / 255.),
 | |
| 		},
 | |
| 		{
 | |
| 			.segmentsCount = 12,
 | |
| 			.minScale = kScaleBigMin / kScaleBigMax,
 | |
| 			.minRadius = st::callMuteMajorBlobMinRadius
 | |
| 				* kScaleBigMax
 | |
| 				* kMainRadiusFactor,
 | |
| 			.maxRadius = st::callMuteMajorBlobMaxRadius
 | |
| 				* kScaleBigMax
 | |
| 				* kMainRadiusFactor,
 | |
| 			.speedScale = 1.,
 | |
| 			.alpha = (76. / 255.),
 | |
| 		},
 | |
| 	};
 | |
| }
 | |
| 
 | |
| auto Colors() {
 | |
| 	using Vector = std::vector<QColor>;
 | |
| 	using Colors = anim::gradient_colors;
 | |
| 	auto result = base::flat_map<CallMuteButtonType, Colors>{
 | |
| 		{
 | |
| 			CallMuteButtonType::Active,
 | |
| 			Colors(Vector{ st::groupCallLive1->c, st::groupCallLive2->c })
 | |
| 		},
 | |
| 		{
 | |
| 			CallMuteButtonType::Connecting,
 | |
| 			Colors(st::callIconBg->c)
 | |
| 		},
 | |
| 		{
 | |
| 			CallMuteButtonType::Muted,
 | |
| 			Colors(Vector{ st::groupCallMuted1->c, st::groupCallMuted2->c })
 | |
| 		},
 | |
| 	};
 | |
| 	const auto forceMutedColors = Colors(QGradientStops{
 | |
| 		{ .0, st::groupCallForceMuted3->c },
 | |
| 		{ .5, st::groupCallForceMuted2->c },
 | |
| 		{ 1., st::groupCallForceMuted1->c } });
 | |
| 	const auto forceMutedTypes = {
 | |
| 		CallMuteButtonType::ForceMuted,
 | |
| 		CallMuteButtonType::RaisedHand,
 | |
| 		CallMuteButtonType::ScheduledCanStart,
 | |
| 		CallMuteButtonType::ScheduledNotify,
 | |
| 		CallMuteButtonType::ScheduledSilent,
 | |
| 	};
 | |
| 	for (const auto type : forceMutedTypes) {
 | |
| 		result.emplace(type, forceMutedColors);
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| bool IsConnecting(CallMuteButtonType type) {
 | |
| 	return (type == CallMuteButtonType::Connecting);
 | |
| }
 | |
| 
 | |
| bool IsInactive(CallMuteButtonType type) {
 | |
| 	return IsConnecting(type);
 | |
| }
 | |
| 
 | |
| auto Clamp(float64 value) {
 | |
| 	return std::clamp(value, 0., 1.);
 | |
| }
 | |
| 
 | |
| void ComputeRadialFinish(
 | |
| 		int &value,
 | |
| 		float64 progress,
 | |
| 		int to = -RadialState::kFull) {
 | |
| 	value = anim::interpolate(value, to, Clamp(progress));
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| class AnimatedLabel final : public RpWidget {
 | |
| public:
 | |
| 	AnimatedLabel(
 | |
| 		QWidget *parent,
 | |
| 		rpl::producer<QString> &&text,
 | |
| 		crl::time duration,
 | |
| 		int additionalHeight,
 | |
| 		const style::FlatLabel &st = st::defaultFlatLabel);
 | |
| 
 | |
| 	int contentHeight() const;
 | |
| 
 | |
| private:
 | |
| 	void setText(const QString &text);
 | |
| 
 | |
| 	const style::FlatLabel &_st;
 | |
| 	const crl::time _duration;
 | |
| 	const int _additionalHeight;
 | |
| 	const TextParseOptions _options;
 | |
| 
 | |
| 	Text::String _text;
 | |
| 	Text::String _previousText;
 | |
| 
 | |
| 	Animations::Simple _animation;
 | |
| 
 | |
| };
 | |
| 
 | |
| AnimatedLabel::AnimatedLabel(
 | |
| 	QWidget *parent,
 | |
| 	rpl::producer<QString> &&text,
 | |
| 	crl::time duration,
 | |
| 	int additionalHeight,
 | |
| 	const style::FlatLabel &st)
 | |
| : RpWidget(parent)
 | |
| , _st(st)
 | |
| , _duration(duration)
 | |
| , _additionalHeight(additionalHeight)
 | |
| , _options({ 0, 0, 0, Qt::LayoutDirectionAuto }) {
 | |
| 	std::move(
 | |
| 		text
 | |
| 	) | rpl::start_with_next([=](const QString &value) {
 | |
| 		setText(value);
 | |
| 	}, lifetime());
 | |
| 
 | |
| 	paintRequest(
 | |
| 	) | rpl::start_with_next([=] {
 | |
| 		Painter p(this);
 | |
| 		const auto progress = _animation.value(1.);
 | |
| 
 | |
| 		p.setFont(_st.style.font);
 | |
| 		p.setPen(_st.textFg);
 | |
| 		p.setTextPalette(_st.palette);
 | |
| 
 | |
| 		const auto textHeight = contentHeight();
 | |
| 		const auto diffHeight = height() - textHeight;
 | |
| 		const auto center = diffHeight / 2;
 | |
| 
 | |
| 		p.setOpacity(1. - progress);
 | |
| 		if (p.opacity()) {
 | |
| 			_previousText.draw(
 | |
| 				p,
 | |
| 				0,
 | |
| 				anim::interpolate(center, diffHeight, progress),
 | |
| 				width(),
 | |
| 				style::al_center);
 | |
| 		}
 | |
| 
 | |
| 		p.setOpacity(progress);
 | |
| 		if (p.opacity()) {
 | |
| 			_text.draw(
 | |
| 				p,
 | |
| 				0,
 | |
| 				anim::interpolate(0, center, progress),
 | |
| 				width(),
 | |
| 				style::al_center);
 | |
| 		}
 | |
| 	}, lifetime());
 | |
| }
 | |
| 
 | |
| int AnimatedLabel::contentHeight() const {
 | |
| 	return _st.style.font->height;
 | |
| }
 | |
| 
 | |
| void AnimatedLabel::setText(const QString &text) {
 | |
| 	if (_text.toString() == text) {
 | |
| 		return;
 | |
| 	}
 | |
| 	_previousText = _text;
 | |
| 	_text.setText(_st.style, text, _options);
 | |
| 
 | |
| 	const auto width = std::max(
 | |
| 		_st.style.font->width(_text.toString()),
 | |
| 		_st.style.font->width(_previousText.toString()));
 | |
| 	resize(
 | |
| 		width + _additionalHeight,
 | |
| 		contentHeight() + _additionalHeight * 2);
 | |
| 
 | |
| 	_animation.stop();
 | |
| 	_animation.start([=] { update(); }, 0., 1., _duration);
 | |
| }
 | |
| 
 | |
| class BlobsWidget final : public RpWidget {
 | |
| public:
 | |
| 	BlobsWidget(
 | |
| 		not_null<RpWidget*> parent,
 | |
| 		int diameter,
 | |
| 		rpl::producer<bool> &&hideBlobs);
 | |
| 
 | |
| 	void setDiameter(int diameter);
 | |
| 	void setLevel(float level);
 | |
| 	void setBlobBrush(QBrush brush);
 | |
| 	void setGlowBrush(QBrush brush);
 | |
| 
 | |
| 	[[nodiscard]] QRectF innerRect() const;
 | |
| 
 | |
| 	[[nodiscard]] float64 switchConnectingProgress() const;
 | |
| 	void setSwitchConnectingProgress(float64 progress);
 | |
| 
 | |
| private:
 | |
| 	void init(int diameter);
 | |
| 	void computeCircleRect();
 | |
| 
 | |
| 	Paint::Blobs _blobs;
 | |
| 
 | |
| 	float _circleRadius = 0.;
 | |
| 	QBrush _blobBrush;
 | |
| 	QBrush _glowBrush;
 | |
| 	int _center = 0;
 | |
| 	QRectF _circleRect;
 | |
| 
 | |
| 	float64 _switchConnectingProgress = 0.;
 | |
| 
 | |
| 	crl::time _blobsLastTime = 0;
 | |
| 	crl::time _blobsHideLastTime = 0;
 | |
| 
 | |
| 	float64 _blobsScaleEnter = 0.;
 | |
| 	crl::time _blobsScaleLastTime = 0;
 | |
| 
 | |
| 	bool _hideBlobs = true;
 | |
| 
 | |
| 	Animations::Basic _animation;
 | |
| 
 | |
| };
 | |
| 
 | |
| BlobsWidget::BlobsWidget(
 | |
| 	not_null<RpWidget*> parent,
 | |
| 	int diameter,
 | |
| 	rpl::producer<bool> &&hideBlobs)
 | |
| : RpWidget(parent)
 | |
| , _blobs(MuteBlobs(), kLevelDuration, kMaxLevel)
 | |
| , _blobBrush(Qt::transparent)
 | |
| , _glowBrush(Qt::transparent)
 | |
| , _blobsLastTime(crl::now())
 | |
| , _blobsScaleLastTime(crl::now()) {
 | |
| 	init(diameter);
 | |
| 
 | |
| 	std::move(
 | |
| 		hideBlobs
 | |
| 	) | rpl::start_with_next([=](bool hide) {
 | |
| 		if (_hideBlobs != hide) {
 | |
| 			const auto now = crl::now();
 | |
| 			if ((now - _blobsScaleLastTime) >= kBlobsScaleEnterDuration) {
 | |
| 				_blobsScaleLastTime = now;
 | |
| 			}
 | |
| 			_hideBlobs = hide;
 | |
| 		}
 | |
| 		if (hide) {
 | |
| 			setLevel(0.);
 | |
| 		}
 | |
| 		_blobsHideLastTime = hide ? crl::now() : 0;
 | |
| 		if (!hide && !_animation.animating()) {
 | |
| 			_animation.start();
 | |
| 		}
 | |
| 	}, lifetime());
 | |
| }
 | |
| 
 | |
| void BlobsWidget::setDiameter(int diameter) {
 | |
| 	_circleRadius = diameter / 2.;
 | |
| 	const auto defaultSize = _blobs.maxRadius() * 2 * kGlowPaddingFactor;
 | |
| 	const auto s = int(std::ceil((defaultSize * diameter)
 | |
| 		/ float64(st::callMuteBlobRadiusForDiameter)));
 | |
| 	const auto size = QSize{ s, s };
 | |
| 	if (this->size() != size) {
 | |
| 		resize(size);
 | |
| 	}
 | |
| 	computeCircleRect();
 | |
| }
 | |
| 
 | |
| void BlobsWidget::computeCircleRect() {
 | |
| 	const auto &r = _circleRadius;
 | |
| 	const auto left = (size().width() - r * 2.) / 2.;
 | |
| 	const auto add = st::callConnectingRadial.thickness / 2;
 | |
| 	_circleRect = QRectF(left, left, r * 2, r * 2).marginsAdded(
 | |
| 		style::margins(add, add, add, add));
 | |
| }
 | |
| 
 | |
| void BlobsWidget::init(int diameter) {
 | |
| 	setAttribute(Qt::WA_TransparentForMouseEvents);
 | |
| 
 | |
| 	const auto cutRect = [](Painter &p, const QRectF &r) {
 | |
| 		p.save();
 | |
| 		p.setOpacity(1.);
 | |
| 		p.setBrush(st::groupCallBg);
 | |
| 		p.setCompositionMode(QPainter::CompositionMode_Source);
 | |
| 		p.drawEllipse(r);
 | |
| 		p.restore();
 | |
| 	};
 | |
| 
 | |
| 	setDiameter(diameter);
 | |
| 
 | |
| 	sizeValue(
 | |
| 	) | rpl::start_with_next([=](QSize size) {
 | |
| 		_center = size.width() / 2;
 | |
| 		computeCircleRect();
 | |
| 	}, lifetime());
 | |
| 
 | |
| 	paintRequest(
 | |
| 	) | rpl::start_with_next([=] {
 | |
| 		Painter p(this);
 | |
| 		PainterHighQualityEnabler hq(p);
 | |
| 
 | |
| 		p.setPen(Qt::NoPen);
 | |
| 
 | |
| 		// Glow.
 | |
| 		const auto s = kGlowMinScale
 | |
| 			+ (1. - kGlowMinScale) * _blobs.currentLevel();
 | |
| 		p.translate(_center, _center);
 | |
| 		p.scale(s, s);
 | |
| 		p.translate(-_center, -_center);
 | |
| 		p.fillRect(rect(), _glowBrush);
 | |
| 		p.resetTransform();
 | |
| 
 | |
| 		// Blobs.
 | |
| 		p.translate(_center, _center);
 | |
| 		const auto scale = (_switchConnectingProgress > 0.)
 | |
| 			? anim::easeOutBack(
 | |
| 				1.,
 | |
| 				_blobsScaleEnter * (1. - Clamp(
 | |
| 					_switchConnectingProgress / kBlobPartAnimation)))
 | |
| 			: _blobsScaleEnter;
 | |
| 		const auto sizeScale = (2. * _circleRadius)
 | |
| 			/ st::callMuteBlobRadiusForDiameter;
 | |
| 		_blobs.paint(p, _blobBrush, scale * sizeScale);
 | |
| 		p.translate(-_center, -_center);
 | |
| 
 | |
| 		if (scale < 1.) {
 | |
| 			cutRect(p, _circleRect);
 | |
| 		}
 | |
| 
 | |
| 		// Main circle.
 | |
| 		const auto circleProgress =
 | |
| 			Clamp(_switchConnectingProgress - kBlobPartAnimation)
 | |
| 				/ kFillCirclePartAnimation;
 | |
| 		const auto skipColoredCircle = (circleProgress == 1.);
 | |
| 
 | |
| 		if (!skipColoredCircle) {
 | |
| 			p.setBrush(_blobBrush);
 | |
| 			p.drawEllipse(_circleRect);
 | |
| 		}
 | |
| 
 | |
| 		if (_switchConnectingProgress > 0.) {
 | |
| 			p.resetTransform();
 | |
| 
 | |
| 			const auto mF = (_circleRect.width() / 2) * (1. - circleProgress);
 | |
| 			const auto cutOutRect = _circleRect.marginsRemoved(
 | |
| 				QMarginsF(mF, mF, mF, mF));
 | |
| 
 | |
| 			if (!skipColoredCircle) {
 | |
| 				p.setBrush(st::callConnectingRadial.color);
 | |
| 				p.setOpacity(circleProgress);
 | |
| 				p.drawEllipse(_circleRect);
 | |
| 			}
 | |
| 
 | |
| 			p.setOpacity(1.);
 | |
| 
 | |
| 			cutRect(p, cutOutRect);
 | |
| 
 | |
| 			p.setBrush(st::callIconBg);
 | |
| 			p.drawEllipse(cutOutRect);
 | |
| 		}
 | |
| 	}, lifetime());
 | |
| 
 | |
| 	_animation.init([=](crl::time now) {
 | |
| 		if (const auto &last = _blobsHideLastTime; (last > 0)
 | |
| 			&& (now - last >= kBlobsScaleEnterDuration)) {
 | |
| 			_animation.stop();
 | |
| 			return false;
 | |
| 		}
 | |
| 		_blobs.updateLevel(now - _blobsLastTime);
 | |
| 		_blobsLastTime = now;
 | |
| 
 | |
| 		const auto dt = Clamp(
 | |
| 			(now - _blobsScaleLastTime) / float64(kBlobsScaleEnterDuration));
 | |
| 		_blobsScaleEnter = _hideBlobs
 | |
| 			? (1. - anim::easeInCirc(1., dt))
 | |
| 			: anim::easeOutBack(1., dt);
 | |
| 
 | |
| 		update();
 | |
| 		return true;
 | |
| 	});
 | |
| 	shownValue(
 | |
| 	) | rpl::start_with_next([=](bool shown) {
 | |
| 		if (shown) {
 | |
| 			_animation.start();
 | |
| 		} else {
 | |
| 			_animation.stop();
 | |
| 		}
 | |
| 	}, lifetime());
 | |
| }
 | |
| 
 | |
| QRectF BlobsWidget::innerRect() const {
 | |
| 	return _circleRect;
 | |
| }
 | |
| 
 | |
| void BlobsWidget::setBlobBrush(QBrush brush) {
 | |
| 	if (_blobBrush == brush) {
 | |
| 		return;
 | |
| 	}
 | |
| 	_blobBrush = brush;
 | |
| }
 | |
| 
 | |
| void BlobsWidget::setGlowBrush(QBrush brush) {
 | |
| 	if (_glowBrush == brush) {
 | |
| 		return;
 | |
| 	}
 | |
| 	_glowBrush = brush;
 | |
| }
 | |
| 
 | |
| void BlobsWidget::setLevel(float level) {
 | |
| 	if (_blobsHideLastTime) {
 | |
| 		 return;
 | |
| 	}
 | |
| 	_blobs.setLevel(level);
 | |
| }
 | |
| 
 | |
| float64 BlobsWidget::switchConnectingProgress() const {
 | |
| 	return _switchConnectingProgress;
 | |
| }
 | |
| 
 | |
| void BlobsWidget::setSwitchConnectingProgress(float64 progress) {
 | |
| 	_switchConnectingProgress = progress;
 | |
| }
 | |
| 
 | |
| CallMuteButton::CallMuteButton(
 | |
| 	not_null<RpWidget*> parent,
 | |
| 	const style::CallMuteButton &st,
 | |
| 	rpl::producer<bool> &&hideBlobs,
 | |
| 	CallMuteButtonState initial)
 | |
| : _state(initial)
 | |
| , _st(&st)
 | |
| , _blobs(base::make_unique_q<BlobsWidget>(
 | |
| 	parent,
 | |
| 	_st->active.bgSize,
 | |
| 	rpl::combine(
 | |
| 		rpl::single(anim::Disabled()) | rpl::then(anim::Disables()),
 | |
| 		std::move(hideBlobs),
 | |
| 		_state.value(
 | |
| 		) | rpl::map([](const CallMuteButtonState &state) {
 | |
| 			return IsInactive(state.type);
 | |
| 		})
 | |
| 	) | rpl::map([](bool animDisabled, bool hide, bool isBadState) {
 | |
| 		return isBadState || !(!animDisabled && !hide);
 | |
| 	})))
 | |
| , _content(base::make_unique_q<AbstractButton>(parent))
 | |
| , _colors(Colors())
 | |
| , _iconState(iconStateFrom(initial.type)) {
 | |
| 	init();
 | |
| }
 | |
| 
 | |
| void CallMuteButton::refreshLabels() {
 | |
| 	_centerLabel = base::make_unique_q<AnimatedLabel>(
 | |
| 		_content->parentWidget(),
 | |
| 		_state.value(
 | |
| 		) | rpl::map([](const CallMuteButtonState &state) {
 | |
| 			return state.subtext.isEmpty() ? state.text : QString();
 | |
| 		}),
 | |
| 		kSwitchLabelDuration,
 | |
| 		_st->labelAdditional,
 | |
| 		_st->active.label);
 | |
| 	_label = base::make_unique_q<AnimatedLabel>(
 | |
| 		_content->parentWidget(),
 | |
| 		_state.value(
 | |
| 		) | rpl::map([](const CallMuteButtonState &state) {
 | |
| 			return state.subtext.isEmpty() ? QString() : state.text;
 | |
| 		}),
 | |
| 		kSwitchLabelDuration,
 | |
| 		_st->labelAdditional,
 | |
| 		_st->active.label);
 | |
| 	_sublabel = base::make_unique_q<AnimatedLabel>(
 | |
| 		_content->parentWidget(),
 | |
| 		_state.value(
 | |
| 		) | rpl::map([](const CallMuteButtonState &state) {
 | |
| 			return state.subtext;
 | |
| 		}),
 | |
| 		kSwitchLabelDuration,
 | |
| 		_st->labelAdditional,
 | |
| 		_st->sublabel);
 | |
| 
 | |
| 	_label->show();
 | |
| 	rpl::combine(
 | |
| 		_content->geometryValue(),
 | |
| 		_label->sizeValue()
 | |
| 	) | rpl::start_with_next([=](QRect my, QSize size) {
 | |
| 		updateLabelGeometry(my, size);
 | |
| 	}, _label->lifetime());
 | |
| 	_label->setAttribute(Qt::WA_TransparentForMouseEvents);
 | |
| 
 | |
| 	_sublabel->show();
 | |
| 	rpl::combine(
 | |
| 		_content->geometryValue(),
 | |
| 		_sublabel->sizeValue()
 | |
| 	) | rpl::start_with_next([=](QRect my, QSize size) {
 | |
| 		updateSublabelGeometry(my, size);
 | |
| 	}, _sublabel->lifetime());
 | |
| 	_sublabel->setAttribute(Qt::WA_TransparentForMouseEvents);
 | |
| 
 | |
| 	_centerLabel->show();
 | |
| 	rpl::combine(
 | |
| 		_content->geometryValue(),
 | |
| 		_centerLabel->sizeValue()
 | |
| 	) | rpl::start_with_next([=](QRect my, QSize size) {
 | |
| 		updateCenterLabelGeometry(my, size);
 | |
| 	}, _centerLabel->lifetime());
 | |
| 	_centerLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
 | |
| }
 | |
| 
 | |
| void CallMuteButton::refreshIcons() {
 | |
| 	_icons[0].emplace(Lottie::IconDescriptor{
 | |
| 		.path = u":/icons/calls/voice.lottie"_q,
 | |
| 		.color = st::groupCallIconFg,
 | |
| 		.sizeOverride = _st->lottieSize,
 | |
| 		.frame = (_iconState.index ? 0 : _iconState.frameTo),
 | |
| 	});
 | |
| 	_icons[1].emplace(Lottie::IconDescriptor{
 | |
| 		.path = u":/icons/calls/hands.lottie"_q,
 | |
| 		.color = st::groupCallIconFg,
 | |
| 		.sizeOverride = _st->lottieSize,
 | |
| 		.frame = (_iconState.index ? _iconState.frameTo : 0),
 | |
| 	});
 | |
| 
 | |
| }
 | |
| 
 | |
| auto CallMuteButton::iconStateAnimated(CallMuteButtonType previous)
 | |
| -> IconState {
 | |
| 	using Type = CallMuteButtonType;
 | |
| 	using Key = std::pair<Type, Type>;
 | |
| 	struct Animation {
 | |
| 		int from = 0;
 | |
| 		int to = 0;
 | |
| 	};
 | |
| 	static const auto kAnimations = std::vector<std::pair<Key, Animation>>{
 | |
| 		{ { Type::ForceMuted, Type::Muted }, { 0, 35 } },
 | |
| 		{ { Type::Muted, Type::Active }, { 36, 68 } },
 | |
| 		{ { Type::Active, Type::Muted }, { 69, 98 } },
 | |
| 		{ { Type::Muted, Type::ForceMuted }, { 99, 135 } },
 | |
| 		{ { Type::Active, Type::ForceMuted }, { 136, 172 } },
 | |
| 		{ { Type::ScheduledSilent, Type::ScheduledNotify }, { 173, 201 } },
 | |
| 		{ { Type::ScheduledSilent, Type::Muted }, { 202, 236 } },
 | |
| 		{ { Type::ScheduledSilent, Type::ForceMuted }, { 237, 273 } },
 | |
| 		{ { Type::ScheduledNotify, Type::ForceMuted }, { 274, 310 } },
 | |
| 		{ { Type::ScheduledNotify, Type::ScheduledSilent }, { 311, 343 } },
 | |
| 		{ { Type::ScheduledNotify, Type::Muted }, { 344, 375 } },
 | |
| 		{ { Type::ScheduledCanStart, Type::Muted }, { 376, 403 } },
 | |
| 	};
 | |
| 	static const auto kMap = [] {
 | |
| 		// flat_multi_map_pair_type lacks some required constructors :(
 | |
| 		auto &&list = kAnimations | ranges::views::transform([](auto &&pair) {
 | |
| 			return base::flat_multi_map_pair_type<Key, Animation>(
 | |
| 				pair.first,
 | |
| 				pair.second);
 | |
| 		});
 | |
| 		return base::flat_map<Key, Animation>(begin(list), end(list));
 | |
| 	}();
 | |
| 	const auto was = TypeForIcon(previous);
 | |
| 	const auto now = TypeForIcon(_state.current().type);
 | |
| 	if (was == now) {
 | |
| 		return {};
 | |
| 	}
 | |
| 
 | |
| 	if (const auto i = kMap.find(Key{ was, now }); i != end(kMap)) {
 | |
| 		return { 0, i->second.from, i->second.to };
 | |
| 	}
 | |
| 	return {};
 | |
| }
 | |
| 
 | |
| CallMuteButton::IconState CallMuteButton::iconStateFrom(
 | |
| 		CallMuteButtonType previous) {
 | |
| 	if (const auto animated = iconStateAnimated(previous)) {
 | |
| 		return animated;
 | |
| 	}
 | |
| 
 | |
| 	using Type = CallMuteButtonType;
 | |
| 	static const auto kFinal = base::flat_map<Type, int>{
 | |
| 		{ Type::ForceMuted, 0 },
 | |
| 		{ Type::Muted, 36 },
 | |
| 		{ Type::Active, 69 },
 | |
| 		{ Type::ScheduledSilent, 173 },
 | |
| 		{ Type::ScheduledNotify, 274 },
 | |
| 		{ Type::ScheduledCanStart, 376 },
 | |
| 	};
 | |
| 
 | |
| 	const auto now = TypeForIcon(_state.current().type);
 | |
| 	const auto i = kFinal.find(now);
 | |
| 
 | |
| 	Ensures(i != end(kFinal));
 | |
| 	return { 0, i->second, i->second };
 | |
| }
 | |
| 
 | |
| CallMuteButton::IconState CallMuteButton::randomWavingState() {
 | |
| 	struct Animation {
 | |
| 		int from = 0;
 | |
| 		int to = 0;
 | |
| 	};
 | |
| 	static const auto kAnimations = std::vector<Animation>{
 | |
| 		{ 0, 120 },
 | |
| 		{ 120, 240 },
 | |
| 		{ 240, 420 },
 | |
| 		{ 420, 540 },
 | |
| 	};
 | |
| 	const auto index = base::RandomIndex(kAnimations.size());
 | |
| 	return { 1, kAnimations[index].from, kAnimations[index].to };
 | |
| }
 | |
| 
 | |
| void CallMuteButton::init() {
 | |
| 	refreshLabels();
 | |
| 	refreshIcons();
 | |
| 
 | |
| 	const auto &button = _st->active.button;
 | |
| 	_content->resize(button.width, button.height);
 | |
| 
 | |
| 	_content->events(
 | |
| 	) | rpl::start_with_next([=](not_null<QEvent*> e) {
 | |
| 		if (e->type() == QEvent::MouseMove) {
 | |
| 			if (!_state.current().tooltip.isEmpty()) {
 | |
| 				Ui::Tooltip::Show(1000, this);
 | |
| 			}
 | |
| 		} else if (e->type() == QEvent::Leave) {
 | |
| 			Ui::Tooltip::Hide();
 | |
| 		}
 | |
| 	}, _content->lifetime());
 | |
| 
 | |
| 	rpl::combine(
 | |
| 		_radialInfo.rawShowProgress.value(),
 | |
| 		anim::Disables()
 | |
| 	) | rpl::start_with_next([=](float64 value, bool disabled) {
 | |
| 		auto &info = _radialInfo;
 | |
| 		info.realShowProgress = (1. - value) / kRadialEndPartAnimation;
 | |
| 
 | |
| 		const auto guard = gsl::finally([&] {
 | |
| 			_content->update();
 | |
| 		});
 | |
| 
 | |
| 		if (((value == 0.) || disabled) && _radial) {
 | |
| 			_radial->stop();
 | |
| 			_radial = nullptr;
 | |
| 			return;
 | |
| 		}
 | |
| 		if ((value > 0.) && !disabled && !_radial) {
 | |
| 			_radial = std::make_unique<InfiniteRadialAnimation>(
 | |
| 				[=] { _content->update(); },
 | |
| 				_radialInfo.st);
 | |
| 			_radial->start();
 | |
| 		}
 | |
| 		if ((info.realShowProgress < 1.) && !info.isDirectionToShow) {
 | |
| 			if (_radial) {
 | |
| 				_radial->stop(anim::type::instant);
 | |
| 				_radial->start();
 | |
| 			}
 | |
| 			info.state = std::nullopt;
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		if (value == 1.) {
 | |
| 			info.state = std::nullopt;
 | |
| 		} else {
 | |
| 			if (_radial && !info.state.has_value()) {
 | |
| 				info.state = _radial->computeState();
 | |
| 			}
 | |
| 		}
 | |
| 	}, lifetime());
 | |
| 
 | |
| 	// State type.
 | |
| 	const auto previousType =
 | |
| 		lifetime().make_state<CallMuteButtonType>(_state.current().type);
 | |
| 	setHandleMouseState(HandleMouseState::Disabled);
 | |
| 
 | |
| 	refreshGradients();
 | |
| 
 | |
| 	_state.value(
 | |
| 	) | rpl::map([](const CallMuteButtonState &state) {
 | |
| 		return state.type;
 | |
| 	}) | rpl::start_with_next([=](CallMuteButtonType type) {
 | |
| 		const auto previous = *previousType;
 | |
| 		*previousType = type;
 | |
| 
 | |
| 		const auto mouseState = HandleMouseStateFromType(type);
 | |
| 		setHandleMouseState(HandleMouseState::Disabled);
 | |
| 		if (mouseState != HandleMouseState::Enabled) {
 | |
| 			setHandleMouseState(mouseState);
 | |
| 		}
 | |
| 
 | |
| 		const auto fromConnecting = IsConnecting(previous);
 | |
| 		const auto toConnecting = IsConnecting(type);
 | |
| 
 | |
| 		const auto radialShowFrom = fromConnecting ? 1. : 0.;
 | |
| 		const auto radialShowTo = toConnecting ? 1. : 0.;
 | |
| 
 | |
| 		const auto from = (_switchAnimation.animating() && !fromConnecting)
 | |
| 			? (1. - _switchAnimation.value(0.))
 | |
| 			: 0.;
 | |
| 		const auto to = 1.;
 | |
| 
 | |
| 		_radialInfo.isDirectionToShow = fromConnecting;
 | |
| 
 | |
| 		scheduleIconState(iconStateFrom(previous));
 | |
| 
 | |
| 		auto callback = [=](float64 value) {
 | |
| 			const auto brushProgress = fromConnecting ? 1. : value;
 | |
| 			_blobs->setBlobBrush(QBrush(
 | |
| 				_linearGradients.gradient(previous, type, brushProgress)));
 | |
| 			_blobs->setGlowBrush(QBrush(
 | |
| 				_glowGradients.gradient(previous, type, value)));
 | |
| 			_blobs->update();
 | |
| 
 | |
| 			const auto radialShowProgress = (radialShowFrom == radialShowTo)
 | |
| 				? radialShowTo
 | |
| 				: anim::interpolateF(radialShowFrom, radialShowTo, value);
 | |
| 			if (radialShowProgress != _radialInfo.rawShowProgress.current()) {
 | |
| 				_radialInfo.rawShowProgress = radialShowProgress;
 | |
| 				_blobs->setSwitchConnectingProgress(Clamp(
 | |
| 					radialShowProgress / kBlobsWidgetPartAnimation));
 | |
| 			}
 | |
| 
 | |
| 			overridesColors(previous, type, value);
 | |
| 
 | |
| 			if (value == to) {
 | |
| 				setHandleMouseState(mouseState);
 | |
| 			}
 | |
| 		};
 | |
| 
 | |
| 		_switchAnimation.stop();
 | |
| 		const auto duration = (1. - from) * ((fromConnecting || toConnecting)
 | |
| 			? kSwitchStateFromConnectingDuration
 | |
| 			: kSwitchStateDuration);
 | |
| 		_switchAnimation.start(std::move(callback), from, to, duration);
 | |
| 	}, lifetime());
 | |
| 
 | |
| 	// Icon rect.
 | |
| 	_content->sizeValue(
 | |
| 	) | rpl::start_with_next([=](QSize size) {
 | |
| 		const auto icon = _st->lottieSize;
 | |
| 		_muteIconRect = QRect(
 | |
| 			(size.width() - icon.width()) / 2,
 | |
| 			_st->lottieTop,
 | |
| 			icon.width(),
 | |
| 			icon.height());
 | |
| 	}, lifetime());
 | |
| 
 | |
| 	// Paint.
 | |
| 	_content->paintRequest(
 | |
| 	) | rpl::start_with_next([=](QRect clip) {
 | |
| 		Painter p(_content);
 | |
| 
 | |
| 		_icons[_iconState.index]->paint(p, _muteIconRect.x(), _muteIconRect.y());
 | |
| 
 | |
| 		if (_radialInfo.state.has_value() && _switchAnimation.animating()) {
 | |
| 			const auto radialProgress = _radialInfo.realShowProgress;
 | |
| 
 | |
| 			auto r = *_radialInfo.state;
 | |
| 			r.shown = 1.;
 | |
| 			if (_radialInfo.isDirectionToShow) {
 | |
| 				const auto to = r.arcFrom - kRadialFinishArcShift;
 | |
| 				ComputeRadialFinish(r.arcFrom, radialProgress, to);
 | |
| 				ComputeRadialFinish(r.arcLength, radialProgress);
 | |
| 			} else {
 | |
| 				r.arcLength = RadialState::kFull;
 | |
| 			}
 | |
| 
 | |
| 			const auto opacity = (radialProgress > kOverlapProgressRadialHide)
 | |
| 				? 0.
 | |
| 				: _blobs->switchConnectingProgress();
 | |
| 			p.setOpacity(opacity);
 | |
| 			InfiniteRadialAnimation::Draw(
 | |
| 				p,
 | |
| 				r,
 | |
| 				_st->active.bgPosition,
 | |
| 				QSize(_st->active.bgSize, _st->active.bgSize),
 | |
| 				_content->width(),
 | |
| 				QPen(_radialInfo.st.color),
 | |
| 				_radialInfo.st.thickness);
 | |
| 		} else if (_radial) {
 | |
| 			auto state = _radial->computeState();
 | |
| 			state.shown = 1.;
 | |
| 
 | |
| 			InfiniteRadialAnimation::Draw(
 | |
| 				p,
 | |
| 				std::move(state),
 | |
| 				_st->active.bgPosition,
 | |
| 				QSize(_st->active.bgSize, _st->active.bgSize),
 | |
| 				_content->width(),
 | |
| 				QPen(_radialInfo.st.color),
 | |
| 				_radialInfo.st.thickness);
 | |
| 		}
 | |
| 	}, _content->lifetime());
 | |
| }
 | |
| 
 | |
| void CallMuteButton::refreshGradients() {
 | |
| 	const auto blobsInner = [&] {
 | |
| 		// The point of the circle at 45 degrees.
 | |
| 		const auto w = _blobs->innerRect().width();
 | |
| 		const auto mF = (1 - std::cos(M_PI / 4.)) * (w / 2.);
 | |
| 		return _blobs->innerRect().marginsRemoved(QMarginsF(mF, mF, mF, mF));
 | |
| 	}();
 | |
| 
 | |
| 	_linearGradients = anim::linear_gradients<CallMuteButtonType>(
 | |
| 		_colors,
 | |
| 		QPointF(blobsInner.x() + blobsInner.width(), blobsInner.y()),
 | |
| 		QPointF(blobsInner.x(), blobsInner.y() + blobsInner.height()));
 | |
| 
 | |
| 	auto glowColors = [&] {
 | |
| 		auto copy = _colors;
 | |
| 		for (auto &[type, stops] : copy) {
 | |
| 			auto firstColor = IsInactive(type)
 | |
| 				? st::groupCallBg->c
 | |
| 				: stops.stops[(stops.stops.size() - 1) / 2].second;
 | |
| 			firstColor.setAlpha(kGlowAlpha);
 | |
| 			stops.stops = QGradientStops{
 | |
| 				{ 0., std::move(firstColor) },
 | |
| 				{ 1., QColor(Qt::transparent) }
 | |
| 			};
 | |
| 		}
 | |
| 		return copy;
 | |
| 	}();
 | |
| 	_glowGradients = anim::radial_gradients<CallMuteButtonType>(
 | |
| 		std::move(glowColors),
 | |
| 		blobsInner.center(),
 | |
| 		_blobs->width() / 2);
 | |
| }
 | |
| 
 | |
| void CallMuteButton::scheduleIconState(const IconState &state) {
 | |
| 	if (_iconState != state) {
 | |
| 		if (_icons[_iconState.index]->animating()) {
 | |
| 			_scheduledState = state;
 | |
| 		} else {
 | |
| 			startIconState(state);
 | |
| 		}
 | |
| 	} else if (_scheduledState) {
 | |
| 		_scheduledState = std::nullopt;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void CallMuteButton::startIconState(const IconState &state) {
 | |
| 	_iconState = state;
 | |
| 	_scheduledState = std::nullopt;
 | |
| 	_icons[_iconState.index]->animate(
 | |
| 		[=] { iconAnimationCallback(); },
 | |
| 		_iconState.frameFrom,
 | |
| 		_iconState.frameTo);
 | |
| }
 | |
| 
 | |
| void CallMuteButton::iconAnimationCallback() {
 | |
| 	_content->update(_muteIconRect);
 | |
| 	if (!_icons[_iconState.index]->animating() && _scheduledState) {
 | |
| 		startIconState(*_scheduledState);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| QString CallMuteButton::tooltipText() const {
 | |
| 	return _state.current().tooltip;
 | |
| }
 | |
| 
 | |
| QPoint CallMuteButton::tooltipPos() const {
 | |
| 	return QCursor::pos();
 | |
| }
 | |
| 
 | |
| bool CallMuteButton::tooltipWindowActive() const {
 | |
| 	return Ui::AppInFocus()
 | |
| 		&& Ui::InFocusChain(_content->window())
 | |
| 		&& _content->mapToGlobal(_content->rect()).contains(QCursor::pos());
 | |
| }
 | |
| 
 | |
| const style::Tooltip *CallMuteButton::tooltipSt() const {
 | |
| 	return &st::groupCallTooltip;
 | |
| }
 | |
| 
 | |
| void CallMuteButton::updateLabelsGeometry() {
 | |
| 	updateLabelGeometry(_content->geometry(), _label->size());
 | |
| 	updateCenterLabelGeometry(_content->geometry(), _centerLabel->size());
 | |
| 	updateSublabelGeometry(_content->geometry(), _sublabel->size());
 | |
| }
 | |
| 
 | |
| void CallMuteButton::updateLabelGeometry(QRect my, QSize size) {
 | |
| 	const auto skip = _st->sublabelSkip + _st->labelsSkip;
 | |
| 	const auto contentHeight = _label->contentHeight();
 | |
| 	const auto contentTop = my.y() + my.height() - contentHeight - skip;
 | |
| 	_label->moveToLeft(
 | |
| 		my.x() + (my.width() - size.width()) / 2 + _labelShakeShift,
 | |
| 		contentTop - (size.height() - contentHeight) / 2,
 | |
| 		my.width());
 | |
| }
 | |
| 
 | |
| void CallMuteButton::updateCenterLabelGeometry(QRect my, QSize size) {
 | |
| 	const auto skip = (_st->sublabelSkip / 2) + _st->labelsSkip;
 | |
| 	const auto contentHeight = _centerLabel->contentHeight();
 | |
| 	const auto contentTop = my.y() + my.height() - contentHeight - skip;
 | |
| 	_centerLabel->moveToLeft(
 | |
| 		my.x() + (my.width() - size.width()) / 2 + _labelShakeShift,
 | |
| 		contentTop - (size.height() - contentHeight) / 2,
 | |
| 		my.width());
 | |
| }
 | |
| 
 | |
| void CallMuteButton::updateSublabelGeometry(QRect my, QSize size) {
 | |
| 	const auto skip = _st->labelsSkip;
 | |
| 	const auto contentHeight = _sublabel->contentHeight();
 | |
| 	const auto contentTop = my.y() + my.height() - contentHeight - skip;
 | |
| 	_sublabel->moveToLeft(
 | |
| 		my.x() + (my.width() - size.width()) / 2 + _labelShakeShift,
 | |
| 		contentTop - (size.height() - contentHeight) / 2,
 | |
| 		my.width());
 | |
| }
 | |
| 
 | |
| void CallMuteButton::shake() {
 | |
| 	if (_shakeAnimation.animating()) {
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto update = [=] {
 | |
| 		const auto fullProgress = _shakeAnimation.value(1.) * 6;
 | |
| 		const auto segment = std::clamp(int(std::floor(fullProgress)), 0, 5);
 | |
| 		const auto part = fullProgress - segment;
 | |
| 		const auto from = (segment == 0)
 | |
| 			? 0.
 | |
| 			: (segment == 1 || segment == 3 || segment == 5)
 | |
| 			? 1.
 | |
| 			: -1.;
 | |
| 		const auto to = (segment == 0 || segment == 2 || segment == 4)
 | |
| 			? 1.
 | |
| 			: (segment == 1 || segment == 3)
 | |
| 			? -1.
 | |
| 			: 0.;
 | |
| 		const auto shift = from * (1. - part) + to * part;
 | |
| 		_labelShakeShift = int(base::SafeRound(shift * st::shakeShift));
 | |
| 		updateLabelsGeometry();
 | |
| 	};
 | |
| 	_shakeAnimation.start(
 | |
| 		update,
 | |
| 		0.,
 | |
| 		1.,
 | |
| 		kShiftDuration);
 | |
| }
 | |
| 
 | |
| CallMuteButton::HandleMouseState CallMuteButton::HandleMouseStateFromType(
 | |
| 		CallMuteButtonType type) {
 | |
| 	switch (type) {
 | |
| 	case CallMuteButtonType::Active:
 | |
| 	case CallMuteButtonType::Muted:
 | |
| 		return HandleMouseState::Enabled;
 | |
| 	case CallMuteButtonType::Connecting:
 | |
| 		return HandleMouseState::Disabled;
 | |
| 	case CallMuteButtonType::ScheduledCanStart:
 | |
| 	case CallMuteButtonType::ScheduledNotify:
 | |
| 	case CallMuteButtonType::ScheduledSilent:
 | |
| 	case CallMuteButtonType::ForceMuted:
 | |
| 	case CallMuteButtonType::RaisedHand:
 | |
| 		return HandleMouseState::Enabled;
 | |
| 	}
 | |
| 	Unexpected("Type in HandleMouseStateFromType.");
 | |
| }
 | |
| 
 | |
| void CallMuteButton::setStyle(const style::CallMuteButton &st) {
 | |
| 	if (_st == &st) {
 | |
| 		return;
 | |
| 	}
 | |
| 	_st = &st;
 | |
| 	const auto &button = _st->active.button;
 | |
| 	_content->resize(button.width, button.height);
 | |
| 	_blobs->setDiameter(_st->active.bgSize);
 | |
| 
 | |
| 	refreshIcons();
 | |
| 	refreshLabels();
 | |
| 	updateLabelsGeometry();
 | |
| 	refreshGradients();
 | |
| }
 | |
| 
 | |
| void CallMuteButton::setState(const CallMuteButtonState &state) {
 | |
| 	_state = state;
 | |
| }
 | |
| 
 | |
| void CallMuteButton::setLevel(float level) {
 | |
| 	_level = level;
 | |
| 	_blobs->setLevel(level);
 | |
| }
 | |
| 
 | |
| rpl::producer<Qt::MouseButton> CallMuteButton::clicks() {
 | |
| 	return _content->clicks() | rpl::before_next([=] {
 | |
| 		const auto type = _state.current().type;
 | |
| 		if (type == CallMuteButtonType::ForceMuted
 | |
| 			|| type == CallMuteButtonType::RaisedHand) {
 | |
| 			scheduleIconState(randomWavingState());
 | |
| 		}
 | |
| 	});
 | |
| }
 | |
| 
 | |
| QSize CallMuteButton::innerSize() const {
 | |
| 	return innerGeometry().size();
 | |
| }
 | |
| 
 | |
| QRect CallMuteButton::innerGeometry() const {
 | |
| 	const auto &skip = _st->active.outerRadius;
 | |
| 	return QRect(
 | |
| 		_content->x(),
 | |
| 		_content->y(),
 | |
| 		_content->width() - 2 * skip,
 | |
| 		_content->width() - 2 * skip);
 | |
| }
 | |
| 
 | |
| void CallMuteButton::moveInner(QPoint position) {
 | |
| 	const auto &skip = _st->active.outerRadius;
 | |
| 	_content->move(position - QPoint(skip, skip));
 | |
| 
 | |
| 	{
 | |
| 		const auto offset = QPoint(
 | |
| 			(_blobs->width() - _content->width()) / 2,
 | |
| 			(_blobs->height() - _content->width()) / 2);
 | |
| 		_blobs->move(_content->pos() - offset);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void CallMuteButton::setVisible(bool visible) {
 | |
| 	_centerLabel->setVisible(visible);
 | |
| 	_label->setVisible(visible);
 | |
| 	_sublabel->setVisible(visible);
 | |
| 	_content->setVisible(visible);
 | |
| 	_blobs->setVisible(visible);
 | |
| }
 | |
| 
 | |
| bool CallMuteButton::isHidden() const {
 | |
| 	return _content->isHidden();
 | |
| }
 | |
| 
 | |
| void CallMuteButton::raise() {
 | |
| 	_blobs->raise();
 | |
| 	_content->raise();
 | |
| 	_centerLabel->raise();
 | |
| 	_label->raise();
 | |
| 	_sublabel->raise();
 | |
| }
 | |
| 
 | |
| void CallMuteButton::lower() {
 | |
| 	_centerLabel->lower();
 | |
| 	_label->lower();
 | |
| 	_sublabel->lower();
 | |
| 	_content->lower();
 | |
| 	_blobs->lower();
 | |
| }
 | |
| 
 | |
| void CallMuteButton::setHandleMouseState(HandleMouseState state) {
 | |
| 	if (_handleMouseState == state) {
 | |
| 		return;
 | |
| 	}
 | |
| 	_handleMouseState = state;
 | |
| 	const auto handle = (_handleMouseState != HandleMouseState::Disabled);
 | |
| 	const auto pointer = (_handleMouseState == HandleMouseState::Enabled);
 | |
| 	_content->setAttribute(Qt::WA_TransparentForMouseEvents, !handle);
 | |
| 	_content->setPointerCursor(pointer);
 | |
| }
 | |
| 
 | |
| void CallMuteButton::overridesColors(
 | |
| 		CallMuteButtonType fromType,
 | |
| 		CallMuteButtonType toType,
 | |
| 		float64 progress) {
 | |
| 	const auto toInactive = IsInactive(toType);
 | |
| 	const auto fromInactive = IsInactive(fromType);
 | |
| 	if (toInactive && (progress == 1)) {
 | |
| 		_colorOverrides = CallButtonColors();
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto &fromStops = _colors.find(fromType)->second.stops;
 | |
| 	const auto &toStops = _colors.find(toType)->second.stops;
 | |
| 	auto from = fromStops[(fromStops.size() - 1) / 2].second;
 | |
| 	auto to = toStops[(toStops.size() - 1) / 2].second;
 | |
| 	auto fromRipple = from;
 | |
| 	auto toRipple = to;
 | |
| 	if (!toInactive) {
 | |
| 		toRipple.setAlpha(kOverrideColorRippleAlpha);
 | |
| 		to.setAlpha(kOverrideColorBgAlpha);
 | |
| 	}
 | |
| 	if (!fromInactive) {
 | |
| 		fromRipple.setAlpha(kOverrideColorRippleAlpha);
 | |
| 		from.setAlpha(kOverrideColorBgAlpha);
 | |
| 	}
 | |
| 	const auto resultBg = anim::color(from, to, progress);
 | |
| 	const auto resultRipple = anim::color(fromRipple, toRipple, progress);
 | |
| 	_colorOverrides = CallButtonColors{ resultBg, resultRipple };
 | |
| }
 | |
| 
 | |
| rpl::producer<CallButtonColors> CallMuteButton::colorOverrides() const {
 | |
| 	return _colorOverrides.value();
 | |
| }
 | |
| 
 | |
| not_null<RpWidget*> CallMuteButton::outer() const {
 | |
| 	return _content.get();
 | |
| }
 | |
| 
 | |
| rpl::lifetime &CallMuteButton::lifetime() {
 | |
| 	return _blobs->lifetime();
 | |
| }
 | |
| 
 | |
| CallMuteButton::~CallMuteButton() = default;
 | |
| 
 | |
| } // namespace Ui
 | 
