248 lines
		
	
	
	
		
			7.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			248 lines
		
	
	
	
		
			7.2 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/widgets/call_button.h"
 | 
						|
 | 
						|
#include "ui/effects/ripple_animation.h"
 | 
						|
#include "ui/painter.h"
 | 
						|
#include "ui/widgets/labels.h"
 | 
						|
#include "styles/style_widgets.h"
 | 
						|
#include "styles/palette.h"
 | 
						|
 | 
						|
namespace Ui {
 | 
						|
namespace {
 | 
						|
 | 
						|
constexpr auto kOuterBounceDuration = crl::time(100);
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
CallButton::CallButton(
 | 
						|
	QWidget *parent,
 | 
						|
	const style::CallButton &stFrom,
 | 
						|
	const style::CallButton *stTo)
 | 
						|
: RippleButton(parent, stFrom.button.ripple)
 | 
						|
, _stFrom(&stFrom)
 | 
						|
, _stTo(stTo) {
 | 
						|
	init();
 | 
						|
}
 | 
						|
 | 
						|
void CallButton::init() {
 | 
						|
	resize(_stFrom->button.width, _stFrom->button.height);
 | 
						|
 | 
						|
	_bgMask = RippleAnimation::ellipseMask(QSize(_stFrom->bgSize, _stFrom->bgSize));
 | 
						|
	_bgFrom = Ui::PixmapFromImage(style::colorizeImage(_bgMask, _stFrom->bg));
 | 
						|
	if (_stTo) {
 | 
						|
		Assert(_stFrom->button.width == _stTo->button.width);
 | 
						|
		Assert(_stFrom->button.height == _stTo->button.height);
 | 
						|
		Assert(_stFrom->bgPosition == _stTo->bgPosition);
 | 
						|
		Assert(_stFrom->bgSize == _stTo->bgSize);
 | 
						|
 | 
						|
		_bg = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
 | 
						|
		_bg.setDevicePixelRatio(style::DevicePixelRatio());
 | 
						|
		_bgTo = Ui::PixmapFromImage(style::colorizeImage(_bgMask, _stTo->bg));
 | 
						|
		_iconMixedMask = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
 | 
						|
		_iconMixedMask.setDevicePixelRatio(style::DevicePixelRatio());
 | 
						|
		_iconFrom = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
 | 
						|
		_iconFrom.setDevicePixelRatio(style::DevicePixelRatio());
 | 
						|
		_iconFrom.fill(Qt::black);
 | 
						|
		{
 | 
						|
			QPainter p(&_iconFrom);
 | 
						|
			p.drawImage(
 | 
						|
				(_stFrom->bgSize
 | 
						|
					- _stFrom->button.icon.width()) / 2,
 | 
						|
				(_stFrom->bgSize
 | 
						|
					- _stFrom->button.icon.height()) / 2,
 | 
						|
				_stFrom->button.icon.instance(Qt::white));
 | 
						|
		}
 | 
						|
		_iconTo = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
 | 
						|
		_iconTo.setDevicePixelRatio(style::DevicePixelRatio());
 | 
						|
		_iconTo.fill(Qt::black);
 | 
						|
		{
 | 
						|
			QPainter p(&_iconTo);
 | 
						|
			p.drawImage(
 | 
						|
				(_stTo->bgSize
 | 
						|
					- _stTo->button.icon.width()) / 2,
 | 
						|
				(_stTo->bgSize
 | 
						|
					- _stTo->button.icon.height()) / 2,
 | 
						|
				_stTo->button.icon.instance(Qt::white));
 | 
						|
		}
 | 
						|
		_iconMixed = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
 | 
						|
		_iconMixed.setDevicePixelRatio(style::DevicePixelRatio());
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void CallButton::setOuterValue(float64 value) {
 | 
						|
	if (_outerValue != value) {
 | 
						|
		_outerAnimation.start([this] {
 | 
						|
			if (_progress == 0. || _progress == 1.) {
 | 
						|
				update();
 | 
						|
			}
 | 
						|
		}, _outerValue, value, kOuterBounceDuration);
 | 
						|
		_outerValue = value;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void CallButton::setText(rpl::producer<QString> text) {
 | 
						|
	_label.create(this, std::move(text), _stFrom->label);
 | 
						|
	_label->show();
 | 
						|
	rpl::combine(
 | 
						|
		sizeValue(),
 | 
						|
		_label->sizeValue()
 | 
						|
	) | rpl::start_with_next([=](QSize my, QSize label) {
 | 
						|
		_label->moveToLeft(
 | 
						|
			(my.width() - label.width()) / 2,
 | 
						|
			my.height() - label.height(),
 | 
						|
			my.width());
 | 
						|
	}, _label->lifetime());
 | 
						|
}
 | 
						|
 | 
						|
void CallButton::setProgress(float64 progress) {
 | 
						|
	_progress = progress;
 | 
						|
	update();
 | 
						|
}
 | 
						|
 | 
						|
void CallButton::paintEvent(QPaintEvent *e) {
 | 
						|
	QPainter p(this);
 | 
						|
 | 
						|
	auto bgPosition = myrtlpoint(_stFrom->bgPosition);
 | 
						|
	auto paintFrom = (_progress == 0.) || !_stTo;
 | 
						|
	auto paintTo = !paintFrom && (_progress == 1.);
 | 
						|
 | 
						|
	auto outerValue = _outerAnimation.value(_outerValue);
 | 
						|
	if (outerValue > 0.) {
 | 
						|
		auto outerRadius = paintFrom ? _stFrom->outerRadius : paintTo ? _stTo->outerRadius : (_stFrom->outerRadius * (1. - _progress) + _stTo->outerRadius * _progress);
 | 
						|
		auto outerPixels = outerValue * outerRadius;
 | 
						|
		auto outerRect = QRectF(myrtlrect(bgPosition.x(), bgPosition.y(), _stFrom->bgSize, _stFrom->bgSize));
 | 
						|
		outerRect = outerRect.marginsAdded(QMarginsF(outerPixels, outerPixels, outerPixels, outerPixels));
 | 
						|
 | 
						|
		PainterHighQualityEnabler hq(p);
 | 
						|
		if (paintFrom) {
 | 
						|
			p.setBrush(_stFrom->outerBg);
 | 
						|
		} else if (paintTo) {
 | 
						|
			p.setBrush(_stTo->outerBg);
 | 
						|
		} else {
 | 
						|
			p.setBrush(anim::brush(_stFrom->outerBg, _stTo->outerBg, _progress));
 | 
						|
		}
 | 
						|
		p.setPen(Qt::NoPen);
 | 
						|
		p.drawEllipse(outerRect);
 | 
						|
	}
 | 
						|
 | 
						|
	if (_bgOverride) {
 | 
						|
		const auto &s = _stFrom->bgSize;
 | 
						|
		p.setPen(Qt::NoPen);
 | 
						|
		p.setBrush(*_bgOverride);
 | 
						|
 | 
						|
		PainterHighQualityEnabler hq(p);
 | 
						|
		p.drawEllipse(QRect(_stFrom->bgPosition, QSize(s, s)));
 | 
						|
	} else if (paintFrom) {
 | 
						|
		p.drawPixmap(bgPosition, _bgFrom);
 | 
						|
	} else if (paintTo) {
 | 
						|
		p.drawPixmap(bgPosition, _bgTo);
 | 
						|
	} else {
 | 
						|
		style::colorizeImage(_bgMask, anim::color(_stFrom->bg, _stTo->bg, _progress), &_bg);
 | 
						|
		p.drawImage(bgPosition, _bg);
 | 
						|
	}
 | 
						|
 | 
						|
	auto rippleColorInterpolated = QColor();
 | 
						|
	auto rippleColorOverride = &rippleColorInterpolated;
 | 
						|
	if (_rippleOverride) {
 | 
						|
		rippleColorOverride = &(*_rippleOverride);
 | 
						|
	} else if (paintFrom) {
 | 
						|
		rippleColorOverride = nullptr;
 | 
						|
	} else if (paintTo) {
 | 
						|
		rippleColorOverride = &_stTo->button.ripple.color->c;
 | 
						|
	} else {
 | 
						|
		rippleColorInterpolated = anim::color(_stFrom->button.ripple.color, _stTo->button.ripple.color, _progress);
 | 
						|
	}
 | 
						|
	paintRipple(p, _stFrom->button.rippleAreaPosition, rippleColorOverride);
 | 
						|
 | 
						|
	auto positionFrom = iconPosition(_stFrom);
 | 
						|
	if (paintFrom) {
 | 
						|
		const auto icon = &_stFrom->button.icon;
 | 
						|
		icon->paint(p, positionFrom, width());
 | 
						|
	} else {
 | 
						|
		auto positionTo = iconPosition(_stTo);
 | 
						|
		if (paintTo) {
 | 
						|
			_stTo->button.icon.paint(p, positionTo, width());
 | 
						|
		} else {
 | 
						|
			mixIconMasks();
 | 
						|
			style::colorizeImage(_iconMixedMask, st::callIconFg->c, &_iconMixed);
 | 
						|
			p.drawImage(myrtlpoint(_stFrom->bgPosition), _iconMixed);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
QPoint CallButton::iconPosition(not_null<const style::CallButton*> st) const {
 | 
						|
	auto result = st->button.iconPosition;
 | 
						|
	if (result.x() < 0) {
 | 
						|
		result.setX((width() - st->button.icon.width()) / 2);
 | 
						|
	}
 | 
						|
	if (result.y() < 0) {
 | 
						|
		result.setY((height() - st->button.icon.height()) / 2);
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void CallButton::mixIconMasks() {
 | 
						|
	_iconMixedMask.fill(Qt::black);
 | 
						|
 | 
						|
	Painter p(&_iconMixedMask);
 | 
						|
	PainterHighQualityEnabler hq(p);
 | 
						|
	auto paintIconMask = [this, &p](const QImage &mask, float64 angle) {
 | 
						|
		auto skipFrom = _stFrom->bgSize / 2;
 | 
						|
		p.translate(skipFrom, skipFrom);
 | 
						|
		p.rotate(angle);
 | 
						|
		p.translate(-skipFrom, -skipFrom);
 | 
						|
		p.drawImage(0, 0, mask);
 | 
						|
	};
 | 
						|
	p.save();
 | 
						|
	paintIconMask(_iconFrom, (_stFrom->angle - _stTo->angle) * _progress);
 | 
						|
	p.restore();
 | 
						|
	p.setOpacity(_progress);
 | 
						|
	paintIconMask(_iconTo, (_stTo->angle - _stFrom->angle) * (1. - _progress));
 | 
						|
}
 | 
						|
 | 
						|
void CallButton::onStateChanged(State was, StateChangeSource source) {
 | 
						|
	RippleButton::onStateChanged(was, source);
 | 
						|
 | 
						|
	auto over = isOver();
 | 
						|
	auto wasOver = static_cast<bool>(was & StateFlag::Over);
 | 
						|
	if (over != wasOver) {
 | 
						|
		update();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void CallButton::setColorOverrides(rpl::producer<CallButtonColors> &&colors) {
 | 
						|
	std::move(
 | 
						|
		colors
 | 
						|
	) | rpl::start_with_next([=](const CallButtonColors &c) {
 | 
						|
		_bgOverride = c.bg;
 | 
						|
		_rippleOverride = c.ripple;
 | 
						|
		update();
 | 
						|
	}, lifetime());
 | 
						|
}
 | 
						|
 | 
						|
void CallButton::setStyle(
 | 
						|
		const style::CallButton &stFrom,
 | 
						|
		const style::CallButton *stTo) {
 | 
						|
	if (_stFrom == &stFrom && _stTo == stTo) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	_stFrom = &stFrom;
 | 
						|
	_stTo = stTo;
 | 
						|
	init();
 | 
						|
	update();
 | 
						|
}
 | 
						|
 | 
						|
QPoint CallButton::prepareRippleStartPosition() const {
 | 
						|
	return mapFromGlobal(QCursor::pos()) - _stFrom->button.rippleAreaPosition;
 | 
						|
}
 | 
						|
 | 
						|
QImage CallButton::prepareRippleMask() const {
 | 
						|
	return RippleAnimation::ellipseMask(QSize(_stFrom->button.rippleAreaSize, _stFrom->button.rippleAreaSize));
 | 
						|
}
 | 
						|
 | 
						|
} // namespace Ui
 |