163 lines
		
	
	
	
		
			4.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			163 lines
		
	
	
	
		
			4.5 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/premium_stars.h"
 | |
| 
 | |
| #include "base/random.h"
 | |
| #include "ui/effects/animation_value_f.h"
 | |
| 
 | |
| #include <QtCore/QtMath>
 | |
| 
 | |
| namespace Ui {
 | |
| namespace Premium {
 | |
| 
 | |
| constexpr auto kDeformationMax = 0.1;
 | |
| 
 | |
| MiniStars::MiniStars(Fn<void(const QRect &r)> updateCallback, bool opaque)
 | |
| : _availableAngles({
 | |
| 	Interval{ -10, 40 },
 | |
| 	Interval{ 180 + 10 - 40, 40 },
 | |
| 	Interval{ 180 + 15, 50 },
 | |
| 	Interval{ -15 - 50, 50 },
 | |
| })
 | |
| , _lifeLength({ 150 / 5, 200 / 5 })
 | |
| , _deathTime({ 1500, 2000 })
 | |
| , _size({ 5, 10 })
 | |
| , _alpha({ opaque ? 100 : 40, opaque ? 100 : 60 })
 | |
| , _sinFactor({ 10, 190 })
 | |
| , _appearProgressTill(0.2)
 | |
| , _disappearProgressAfter(0.8)
 | |
| , _distanceProgressStart(0.5)
 | |
| , _sprite(u":/gui/icons/settings/starmini.svg"_q)
 | |
| , _animation([=](crl::time now) {
 | |
| 	if (now > _nextBirthTime && !_paused) {
 | |
| 		createStar(now);
 | |
| 	}
 | |
| 	if (_rectToUpdate.isValid()) {
 | |
| 		updateCallback(base::take(_rectToUpdate));
 | |
| 	}
 | |
| }) {
 | |
| 	if (anim::Disabled()) {
 | |
| 		const auto from = _deathTime.from + _deathTime.length;
 | |
| 		auto r = bytes::vector(from + 1);
 | |
| 		base::RandomFill(r.data(), r.size());
 | |
| 
 | |
| 		for (auto i = -from; i < 0; i += randomInterval(_lifeLength, r[-i])) {
 | |
| 			createStar(i);
 | |
| 		}
 | |
| 		updateCallback(_rectToUpdate);
 | |
| 	} else {
 | |
| 		_animation.start();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int MiniStars::randomInterval(
 | |
| 		const Interval &interval,
 | |
| 		const bytes::type &random) const {
 | |
| 	return interval.from + (uchar(random) % interval.length);
 | |
| }
 | |
| 
 | |
| crl::time MiniStars::timeNow() const {
 | |
| 	return anim::Disabled() ? 0 : crl::now();
 | |
| }
 | |
| 
 | |
| void MiniStars::paint(QPainter &p, const QRectF &rect) {
 | |
| 	const auto center = rect.center();
 | |
| 	const auto opacity = p.opacity();
 | |
| 	const auto now = timeNow();
 | |
| 	for (const auto &ministar : _ministars) {
 | |
| 		const auto progress = (now - ministar.birthTime)
 | |
| 			/ float64(ministar.deathTime - ministar.birthTime);
 | |
| 		if (progress > 1.) {
 | |
| 			continue;
 | |
| 		}
 | |
| 		const auto appearProgress = std::clamp(
 | |
| 			progress / _appearProgressTill,
 | |
| 			0.,
 | |
| 			1.);
 | |
| 		const auto rsin = float(std::sin(ministar.angle * M_PI / 180.));
 | |
| 		const auto rcos = float(std::cos(ministar.angle * M_PI / 180.));
 | |
| 		const auto end = QPointF(
 | |
| 			rect.width() / kSizeFactor * rcos,
 | |
| 			rect.height() / kSizeFactor * rsin);
 | |
| 
 | |
| 		const auto alphaProgress = 1.
 | |
| 			- (std::clamp(progress - _disappearProgressAfter, 0., 1.)
 | |
| 				/ (1. - _disappearProgressAfter));
 | |
| 		p.setOpacity(ministar.alpha
 | |
| 			* alphaProgress
 | |
| 			* appearProgress
 | |
| 			* opacity);
 | |
| 
 | |
| 		const auto deformResult = progress * 360;
 | |
| 		const auto rsinDeform = float(
 | |
| 			std::sin(ministar.sinFactor * deformResult * M_PI / 180.));
 | |
| 		const auto deformH = 1. + kDeformationMax * rsinDeform;
 | |
| 		const auto deformW = 1. / deformH;
 | |
| 
 | |
| 		const auto distanceProgress = _distanceProgressStart + progress;
 | |
| 		const auto starSide = ministar.size * appearProgress;
 | |
| 		const auto widthFade = (std::abs(rcos) >= std::abs(rsin));
 | |
| 		const auto starWidth = starSide
 | |
| 			* (widthFade ? alphaProgress : 1.)
 | |
| 			* deformW;
 | |
| 		const auto starHeight = starSide
 | |
| 			* (!widthFade ? alphaProgress : 1.)
 | |
| 			* deformH;
 | |
| 		const auto renderRect = QRectF(
 | |
| 			center.x()
 | |
| 				+ anim::interpolateF(0, end.x(), distanceProgress)
 | |
| 				- starWidth / 2.,
 | |
| 			center.y()
 | |
| 				+ anim::interpolateF(0, end.y(), distanceProgress)
 | |
| 				- starHeight / 2.,
 | |
| 			starWidth,
 | |
| 			starHeight);
 | |
| 		_sprite.render(&p, renderRect);
 | |
| 		_rectToUpdate |= renderRect.toRect();
 | |
| 	}
 | |
| 	p.setOpacity(opacity);
 | |
| }
 | |
| 
 | |
| void MiniStars::setPaused(bool paused) {
 | |
| 	_paused = paused;
 | |
| }
 | |
| 
 | |
| void MiniStars::createStar(crl::time now) {
 | |
| 	constexpr auto kRandomSize = 8;
 | |
| 	auto random = bytes::vector(kRandomSize);
 | |
| 	base::RandomFill(random.data(), random.size());
 | |
| 
 | |
| 	auto i = 0;
 | |
| 	auto next = [&] { return random[i++]; };
 | |
| 
 | |
| 	_nextBirthTime = now + randomInterval(_lifeLength, next());
 | |
| 
 | |
| 	const auto &angleInterval = _availableAngles[
 | |
| 		uchar(next()) % _availableAngles.size()];
 | |
| 
 | |
| 	auto ministar = MiniStar{
 | |
| 		.birthTime = now,
 | |
| 		.deathTime = now + randomInterval(_deathTime, next()),
 | |
| 		.angle = randomInterval(angleInterval, next()),
 | |
| 		.size = float64(randomInterval(_size, next())),
 | |
| 		.alpha = float64(randomInterval(_alpha, next())) / 100.,
 | |
| 		.sinFactor = randomInterval(_sinFactor, next()) / 100.
 | |
| 			* ((uchar(next()) % 2) == 1 ? 1. : -1.),
 | |
| 	};
 | |
| 	for (auto i = 0; i < _ministars.size(); i++) {
 | |
| 		if (ministar.birthTime > _ministars[i].deathTime) {
 | |
| 			_ministars[i] = ministar;
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| 	_ministars.push_back(ministar);
 | |
| }
 | |
| 
 | |
| 
 | |
| } // namespace Premium
 | |
| } // namespace Ui
 | 
