249 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			249 lines
		
	
	
	
		
			6.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/effects/ripple_animation.h"
 | |
| 
 | |
| #include "ui/effects/animations.h"
 | |
| #include "ui/painter.h"
 | |
| #include "ui/ui_utility.h"
 | |
| 
 | |
| namespace Ui {
 | |
| 
 | |
| class RippleAnimation::Ripple {
 | |
| public:
 | |
| 	Ripple(const style::RippleAnimation &st, QPoint origin, int startRadius, const QPixmap &mask, Fn<void()> update);
 | |
| 	Ripple(const style::RippleAnimation &st, const QPixmap &mask, Fn<void()> update);
 | |
| 
 | |
| 	void paint(QPainter &p, const QPixmap &mask, const QColor *colorOverride);
 | |
| 
 | |
| 	void stop();
 | |
| 	void unstop();
 | |
| 	void finish();
 | |
| 	void clearCache();
 | |
| 	bool finished() const {
 | |
| 		return _hiding && !_hide.animating();
 | |
| 	}
 | |
| 
 | |
| private:
 | |
| 	const style::RippleAnimation &_st;
 | |
| 	Fn<void()> _update;
 | |
| 
 | |
| 	QPoint _origin;
 | |
| 	int _radiusFrom = 0;
 | |
| 	int _radiusTo = 0;
 | |
| 
 | |
| 	bool _hiding = false;
 | |
| 	Ui::Animations::Simple _show;
 | |
| 	Ui::Animations::Simple _hide;
 | |
| 	QPixmap _cache;
 | |
| 	QImage _frame;
 | |
| 
 | |
| };
 | |
| 
 | |
| RippleAnimation::Ripple::Ripple(const style::RippleAnimation &st, QPoint origin, int startRadius, const QPixmap &mask, Fn<void()> update)
 | |
| : _st(st)
 | |
| , _update(update)
 | |
| , _origin(origin)
 | |
| , _radiusFrom(startRadius)
 | |
| , _frame(mask.size(), QImage::Format_ARGB32_Premultiplied) {
 | |
| 	_frame.setDevicePixelRatio(mask.devicePixelRatio());
 | |
| 
 | |
| 	const auto pixelRatio = style::DevicePixelRatio();
 | |
| 	QPoint points[] = {
 | |
| 		{ 0, 0 },
 | |
| 		{ _frame.width() / pixelRatio, 0 },
 | |
| 		{ _frame.width() / pixelRatio, _frame.height() / pixelRatio },
 | |
| 		{ 0, _frame.height() / pixelRatio },
 | |
| 	};
 | |
| 	for (auto point : points) {
 | |
| 		accumulate_max(_radiusTo, style::point::dotProduct(_origin - point, _origin - point));
 | |
| 	}
 | |
| 	_radiusTo = qRound(sqrt(_radiusTo));
 | |
| 
 | |
| 	_show.start(_update, 0., 1., _st.showDuration, anim::easeOutQuint);
 | |
| }
 | |
| 
 | |
| RippleAnimation::Ripple::Ripple(const style::RippleAnimation &st, const QPixmap &mask, Fn<void()> update)
 | |
| : _st(st)
 | |
| , _update(update)
 | |
| , _origin(
 | |
| 	mask.width() / (2 * style::DevicePixelRatio()),
 | |
| 	mask.height() / (2 * style::DevicePixelRatio()))
 | |
| , _radiusFrom(mask.width() + mask.height())
 | |
| , _frame(mask.size(), QImage::Format_ARGB32_Premultiplied) {
 | |
| 	_frame.setDevicePixelRatio(mask.devicePixelRatio());
 | |
| 	_radiusTo = _radiusFrom;
 | |
| 	_hide.start(_update, 0., 1., _st.hideDuration);
 | |
| }
 | |
| 
 | |
| void RippleAnimation::Ripple::paint(QPainter &p, const QPixmap &mask, const QColor *colorOverride) {
 | |
| 	auto opacity = _hide.value(_hiding ? 0. : 1.);
 | |
| 	if (opacity == 0.) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (_cache.isNull() || colorOverride != nullptr) {
 | |
| 		auto radius = anim::interpolate(_radiusFrom, _radiusTo, _show.value(1.));
 | |
| 		_frame.fill(Qt::transparent);
 | |
| 		{
 | |
| 			QPainter 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 = PixmapFromImage(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(_update, 1., 0., _st.hideDuration);
 | |
| }
 | |
| 
 | |
| void RippleAnimation::Ripple::unstop() {
 | |
| 	if (_hiding) {
 | |
| 		if (_hide.animating()) {
 | |
| 			_hide.start(_update, 0., 1., _st.hideDuration);
 | |
| 		}
 | |
| 		_hiding = false;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void RippleAnimation::Ripple::finish() {
 | |
| 	if (_update) {
 | |
| 		_update();
 | |
| 	}
 | |
| 	_show.stop();
 | |
| 	_hide.stop();
 | |
| }
 | |
| 
 | |
| void RippleAnimation::Ripple::clearCache() {
 | |
| 	_cache = QPixmap();
 | |
| }
 | |
| 
 | |
| RippleAnimation::RippleAnimation(const style::RippleAnimation &st, QImage mask, Fn<void()> callback)
 | |
| : _st(st)
 | |
| , _mask(PixmapFromImage(std::move(mask)))
 | |
| , _update(callback) {
 | |
| }
 | |
| 
 | |
| 
 | |
| void RippleAnimation::add(QPoint origin, int startRadius) {
 | |
| 	lastStop();
 | |
| 	_ripples.push_back(std::make_unique<Ripple>(_st, origin, startRadius, _mask, _update));
 | |
| }
 | |
| 
 | |
| void RippleAnimation::addFading() {
 | |
| 	lastStop();
 | |
| 	_ripples.push_back(std::make_unique<Ripple>(_st, _mask, _update));
 | |
| }
 | |
| 
 | |
| void RippleAnimation::lastStop() {
 | |
| 	if (!_ripples.empty()) {
 | |
| 		_ripples.back()->stop();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void RippleAnimation::lastUnstop() {
 | |
| 	if (!_ripples.empty()) {
 | |
| 		_ripples.back()->unstop();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void RippleAnimation::lastFinish() {
 | |
| 	if (!_ripples.empty()) {
 | |
| 		_ripples.back()->finish();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void RippleAnimation::forceRepaint() {
 | |
| 	for (const auto &ripple : _ripples) {
 | |
| 		ripple->clearCache();
 | |
| 	}
 | |
| 	if (_update) {
 | |
| 		_update();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void RippleAnimation::paint(QPainter &p, int x, int y, int outerWidth, const QColor *colorOverride) {
 | |
| 	if (_ripples.empty()) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (style::RightToLeft()) {
 | |
| 		x = outerWidth - x - (_mask.width() / style::DevicePixelRatio());
 | |
| 	}
 | |
| 	p.translate(x, y);
 | |
| 	for (const auto &ripple : _ripples) {
 | |
| 		ripple->paint(p, _mask, colorOverride);
 | |
| 	}
 | |
| 	p.translate(-x, -y);
 | |
| 	clearFinished();
 | |
| }
 | |
| 
 | |
| QImage RippleAnimation::maskByDrawer(QSize size, bool filled, Fn<void(QPainter &p)> drawer) {
 | |
| 	auto result = QImage(size * style::DevicePixelRatio(), QImage::Format_ARGB32_Premultiplied);
 | |
| 	result.setDevicePixelRatio(style::DevicePixelRatio());
 | |
| 	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.empty() && _ripples.front()->finished()) {
 | |
| 		_ripples.pop_front();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void RippleAnimation::clear() {
 | |
| 	_ripples.clear();
 | |
| }
 | |
| 
 | |
| RippleAnimation::~RippleAnimation() = default;
 | |
| 
 | |
| } // namespace Ui
 | 
