base::lambda -> Fn (type alias for std::function). base::lambda_once -> FnMut (type alias for base::unique_function). base::lambda_guarded -> crl::guard. base::lambda_call_type_t -> crl::deduced_call_type.
		
			
				
	
	
		
			228 lines
		
	
	
	
		
			6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
	
		
			6 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 "ui/effects/ripple_animation.h"
 | 
						|
 | 
						|
namespace Ui {
 | 
						|
 | 
						|
class RippleAnimation::Ripple {
 | 
						|
public:
 | 
						|
	Ripple(const style::RippleAnimation &st, QPoint origin, int startRadius, const QPixmap &mask, const UpdateCallback &update);
 | 
						|
	Ripple(const style::RippleAnimation &st, const QPixmap &mask, const UpdateCallback &update);
 | 
						|
 | 
						|
	void paint(QPainter &p, const QPixmap &mask, TimeMs ms, const QColor *colorOverride);
 | 
						|
 | 
						|
	void stop();
 | 
						|
	void unstop();
 | 
						|
	void finish();
 | 
						|
	bool finished() const {
 | 
						|
		return _hiding && !_hide.animating();
 | 
						|
	}
 | 
						|
 | 
						|
private:
 | 
						|
	const style::RippleAnimation &_st;
 | 
						|
	UpdateCallback _update;
 | 
						|
 | 
						|
	QPoint _origin;
 | 
						|
	int _radiusFrom = 0;
 | 
						|
	int _radiusTo = 0;
 | 
						|
 | 
						|
	bool _hiding = false;
 | 
						|
	Animation _show;
 | 
						|
	Animation _hide;
 | 
						|
	QPixmap _cache;
 | 
						|
	QImage _frame;
 | 
						|
 | 
						|
};
 | 
						|
 | 
						|
RippleAnimation::Ripple::Ripple(const style::RippleAnimation &st, QPoint origin, int startRadius, const QPixmap &mask, const UpdateCallback &update)
 | 
						|
: _st(st)
 | 
						|
, _update(update)
 | 
						|
, _origin(origin)
 | 
						|
, _radiusFrom(startRadius)
 | 
						|
, _frame(mask.size(), QImage::Format_ARGB32_Premultiplied) {
 | 
						|
	_frame.setDevicePixelRatio(mask.devicePixelRatio());
 | 
						|
 | 
						|
	QPoint points[] = {
 | 
						|
		{ 0, 0 },
 | 
						|
		{ _frame.width() / cIntRetinaFactor(), 0 },
 | 
						|
		{ _frame.width() / cIntRetinaFactor(), _frame.height() / cIntRetinaFactor() },
 | 
						|
		{ 0, _frame.height() / cIntRetinaFactor() },
 | 
						|
	};
 | 
						|
	for (auto point : points) {
 | 
						|
		accumulate_max(_radiusTo, style::point::dotProduct(_origin - point, _origin - point));
 | 
						|
	}
 | 
						|
	_radiusTo = qRound(sqrt(_radiusTo));
 | 
						|
 | 
						|
	_show.start(UpdateCallback(_update), 0., 1., _st.showDuration, anim::easeOutQuint);
 | 
						|
}
 | 
						|
 | 
						|
RippleAnimation::Ripple::Ripple(const style::RippleAnimation &st, const QPixmap &mask, const UpdateCallback &update)
 | 
						|
: _st(st)
 | 
						|
, _update(update)
 | 
						|
, _origin(mask.width() / (2 * cIntRetinaFactor()), mask.height() / (2 * cIntRetinaFactor()))
 | 
						|
, _radiusFrom(mask.width() + mask.height())
 | 
						|
, _frame(mask.size(), QImage::Format_ARGB32_Premultiplied) {
 | 
						|
	_frame.setDevicePixelRatio(mask.devicePixelRatio());
 | 
						|
	_radiusTo = _radiusFrom;
 | 
						|
	_hide.start(UpdateCallback(_update), 0., 1., _st.hideDuration);
 | 
						|
}
 | 
						|
 | 
						|
void RippleAnimation::Ripple::paint(QPainter &p, const QPixmap &mask, TimeMs ms, const QColor *colorOverride) {
 | 
						|
	auto opacity = _hide.current(ms, _hiding ? 0. : 1.);
 | 
						|
	if (opacity == 0.) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (_cache.isNull() || colorOverride != nullptr) {
 | 
						|
		auto radius = anim::interpolate(_radiusFrom, _radiusTo, _show.current(ms, 1.));
 | 
						|
		_frame.fill(Qt::transparent);
 | 
						|
		{
 | 
						|
			Painter p(&_frame);
 | 
						|
			p.setPen(Qt::NoPen);
 | 
						|
			if (colorOverride) {
 | 
						|
				p.setBrush(*colorOverride);
 | 
						|
			} else {
 | 
						|
				p.setBrush(_st.color);
 | 
						|
			}
 | 
						|
			{
 | 
						|
				PainterHighQualityEnabler hq(p);
 | 
						|
				p.drawEllipse(_origin, radius, radius);
 | 
						|
			}
 | 
						|
			p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
 | 
						|
			p.drawPixmap(0, 0, mask);
 | 
						|
		}
 | 
						|
		if (radius == _radiusTo && colorOverride == nullptr) {
 | 
						|
			_cache = App::pixmapFromImageInPlace(std::move(_frame));
 | 
						|
		}
 | 
						|
	}
 | 
						|
	auto saved = p.opacity();
 | 
						|
	if (opacity != 1.) p.setOpacity(saved * opacity);
 | 
						|
	if (_cache.isNull()) {
 | 
						|
		p.drawImage(0, 0, _frame);
 | 
						|
	} else {
 | 
						|
		p.drawPixmap(0, 0, _cache);
 | 
						|
	}
 | 
						|
	if (opacity != 1.) p.setOpacity(saved);
 | 
						|
}
 | 
						|
 | 
						|
void RippleAnimation::Ripple::stop() {
 | 
						|
	_hiding = true;
 | 
						|
	_hide.start(UpdateCallback(_update), 1., 0., _st.hideDuration);
 | 
						|
}
 | 
						|
 | 
						|
void RippleAnimation::Ripple::unstop() {
 | 
						|
	if (_hiding) {
 | 
						|
		if (_hide.animating()) {
 | 
						|
			_hide.start(UpdateCallback(_update), 0., 1., _st.hideDuration);
 | 
						|
		}
 | 
						|
		_hiding = false;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void RippleAnimation::Ripple::finish() {
 | 
						|
	if (_update) {
 | 
						|
		_update();
 | 
						|
	}
 | 
						|
	_show.finish();
 | 
						|
	_hide.finish();
 | 
						|
}
 | 
						|
 | 
						|
RippleAnimation::RippleAnimation(const style::RippleAnimation &st, QImage mask, const UpdateCallback &callback)
 | 
						|
: _st(st)
 | 
						|
, _mask(App::pixmapFromImageInPlace(std::move(mask)))
 | 
						|
, _update(callback) {
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void RippleAnimation::add(QPoint origin, int startRadius) {
 | 
						|
	lastStop();
 | 
						|
	_ripples.push_back(new Ripple(_st, origin, startRadius, _mask, _update));
 | 
						|
}
 | 
						|
 | 
						|
void RippleAnimation::addFading() {
 | 
						|
	lastStop();
 | 
						|
	_ripples.push_back(new Ripple(_st, _mask, _update));
 | 
						|
}
 | 
						|
 | 
						|
void RippleAnimation::lastStop() {
 | 
						|
	if (!_ripples.isEmpty()) {
 | 
						|
		_ripples.back()->stop();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void RippleAnimation::lastUnstop() {
 | 
						|
	if (!_ripples.isEmpty()) {
 | 
						|
		_ripples.back()->unstop();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void RippleAnimation::lastFinish() {
 | 
						|
	if (!_ripples.isEmpty()) {
 | 
						|
		_ripples.back()->finish();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void RippleAnimation::paint(QPainter &p, int x, int y, int outerWidth, TimeMs ms, const QColor *colorOverride) {
 | 
						|
	if (_ripples.isEmpty()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (rtl()) x = outerWidth - x - (_mask.width() / cIntRetinaFactor());
 | 
						|
	p.translate(x, y);
 | 
						|
	for (auto ripple : _ripples) {
 | 
						|
		ripple->paint(p, _mask, ms, colorOverride);
 | 
						|
	}
 | 
						|
	p.translate(-x, -y);
 | 
						|
	clearFinished();
 | 
						|
}
 | 
						|
 | 
						|
QImage RippleAnimation::maskByDrawer(QSize size, bool filled, Fn<void(QPainter &p)> drawer) {
 | 
						|
	auto result = QImage(size * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
 | 
						|
	result.setDevicePixelRatio(cRetinaFactor());
 | 
						|
	result.fill(filled ? QColor(255, 255, 255) : Qt::transparent);
 | 
						|
	if (drawer) {
 | 
						|
		Painter p(&result);
 | 
						|
		PainterHighQualityEnabler hq(p);
 | 
						|
 | 
						|
		p.setPen(Qt::NoPen);
 | 
						|
		p.setBrush(QColor(255, 255, 255));
 | 
						|
		drawer(p);
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
QImage RippleAnimation::rectMask(QSize size) {
 | 
						|
	return maskByDrawer(size, true, Fn<void(QPainter&)>());
 | 
						|
}
 | 
						|
 | 
						|
QImage RippleAnimation::roundRectMask(QSize size, int radius) {
 | 
						|
	return maskByDrawer(size, false, [size, radius](QPainter &p) {
 | 
						|
		p.drawRoundedRect(0, 0, size.width(), size.height(), radius, radius);
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
QImage RippleAnimation::ellipseMask(QSize size) {
 | 
						|
	return maskByDrawer(size, false, [size](QPainter &p) {
 | 
						|
		p.drawEllipse(0, 0, size.width(), size.height());
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
void RippleAnimation::clearFinished() {
 | 
						|
	while (!_ripples.isEmpty() && _ripples.front()->finished()) {
 | 
						|
		delete base::take(_ripples.front());
 | 
						|
		_ripples.pop_front();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void RippleAnimation::clear() {
 | 
						|
	for (auto ripple : base::take(_ripples)) {
 | 
						|
		delete ripple;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
} // namespace Ui
 |