1 // This file is part of Desktop App Toolkit,
2 // a set of libraries for developing nice desktop applications.
3 //
4 // For license and copyright information please follow this link:
5 // https://github.com/desktop-app/legal/blob/master/LEGAL
6 //
7 #include "ui/effects/ripple_animation.h"
8 
9 #include "ui/effects/animations.h"
10 #include "ui/painter.h"
11 #include "ui/ui_utility.h"
12 
13 namespace Ui {
14 
15 class RippleAnimation::Ripple {
16 public:
17 	Ripple(const style::RippleAnimation &st, QPoint origin, int startRadius, const QPixmap &mask, Fn<void()> update);
18 	Ripple(const style::RippleAnimation &st, const QPixmap &mask, Fn<void()> update);
19 
20 	void paint(QPainter &p, const QPixmap &mask, const QColor *colorOverride);
21 
22 	void stop();
23 	void unstop();
24 	void finish();
25 	void clearCache();
finished() const26 	bool finished() const {
27 		return _hiding && !_hide.animating();
28 	}
29 
30 private:
31 	const style::RippleAnimation &_st;
32 	Fn<void()> _update;
33 
34 	QPoint _origin;
35 	int _radiusFrom = 0;
36 	int _radiusTo = 0;
37 
38 	bool _hiding = false;
39 	Ui::Animations::Simple _show;
40 	Ui::Animations::Simple _hide;
41 	QPixmap _cache;
42 	QImage _frame;
43 
44 };
45 
Ripple(const style::RippleAnimation & st,QPoint origin,int startRadius,const QPixmap & mask,Fn<void ()> update)46 RippleAnimation::Ripple::Ripple(const style::RippleAnimation &st, QPoint origin, int startRadius, const QPixmap &mask, Fn<void()> update)
47 : _st(st)
48 , _update(update)
49 , _origin(origin)
50 , _radiusFrom(startRadius)
51 , _frame(mask.size(), QImage::Format_ARGB32_Premultiplied) {
52 	_frame.setDevicePixelRatio(mask.devicePixelRatio());
53 
54 	const auto pixelRatio = style::DevicePixelRatio();
55 	QPoint points[] = {
56 		{ 0, 0 },
57 		{ _frame.width() / pixelRatio, 0 },
58 		{ _frame.width() / pixelRatio, _frame.height() / pixelRatio },
59 		{ 0, _frame.height() / pixelRatio },
60 	};
61 	for (auto point : points) {
62 		accumulate_max(_radiusTo, style::point::dotProduct(_origin - point, _origin - point));
63 	}
64 	_radiusTo = qRound(sqrt(_radiusTo));
65 
66 	_show.start(_update, 0., 1., _st.showDuration, anim::easeOutQuint);
67 }
68 
Ripple(const style::RippleAnimation & st,const QPixmap & mask,Fn<void ()> update)69 RippleAnimation::Ripple::Ripple(const style::RippleAnimation &st, const QPixmap &mask, Fn<void()> update)
70 : _st(st)
71 , _update(update)
72 , _origin(
73 	mask.width() / (2 * style::DevicePixelRatio()),
74 	mask.height() / (2 * style::DevicePixelRatio()))
75 , _radiusFrom(mask.width() + mask.height())
76 , _frame(mask.size(), QImage::Format_ARGB32_Premultiplied) {
77 	_frame.setDevicePixelRatio(mask.devicePixelRatio());
78 	_radiusTo = _radiusFrom;
79 	_hide.start(_update, 0., 1., _st.hideDuration);
80 }
81 
paint(QPainter & p,const QPixmap & mask,const QColor * colorOverride)82 void RippleAnimation::Ripple::paint(QPainter &p, const QPixmap &mask, const QColor *colorOverride) {
83 	auto opacity = _hide.value(_hiding ? 0. : 1.);
84 	if (opacity == 0.) {
85 		return;
86 	}
87 
88 	if (_cache.isNull() || colorOverride != nullptr) {
89 		const auto shown = _show.value(1.);
90 		Assert(!std::isnan(shown));
91 		const auto diff = float64(_radiusTo - _radiusFrom);
92 		Assert(!std::isnan(diff));
93 		const auto mult = diff * shown;
94 		Assert(!std::isnan(mult));
95 		const auto interpolated = _radiusFrom + mult;//anim::interpolateF(_radiusFrom, _radiusTo, shown);
96 		Assert(!std::isnan(interpolated));
97 		auto radius = int(base::SafeRound(interpolated));//anim::interpolate(_radiusFrom, _radiusTo, _show.value(1.));
98 		_frame.fill(Qt::transparent);
99 		{
100 			QPainter p(&_frame);
101 			p.setPen(Qt::NoPen);
102 			if (colorOverride) {
103 				p.setBrush(*colorOverride);
104 			} else {
105 				p.setBrush(_st.color);
106 			}
107 			{
108 				PainterHighQualityEnabler hq(p);
109 				p.drawEllipse(_origin, radius, radius);
110 			}
111 			p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
112 			p.drawPixmap(0, 0, mask);
113 		}
114 		if (radius == _radiusTo && colorOverride == nullptr) {
115 			_cache = PixmapFromImage(std::move(_frame));
116 		}
117 	}
118 	auto saved = p.opacity();
119 	if (opacity != 1.) p.setOpacity(saved * opacity);
120 	if (_cache.isNull()) {
121 		p.drawImage(0, 0, _frame);
122 	} else {
123 		p.drawPixmap(0, 0, _cache);
124 	}
125 	if (opacity != 1.) p.setOpacity(saved);
126 }
127 
stop()128 void RippleAnimation::Ripple::stop() {
129 	_hiding = true;
130 	_hide.start(_update, 1., 0., _st.hideDuration);
131 }
132 
unstop()133 void RippleAnimation::Ripple::unstop() {
134 	if (_hiding) {
135 		if (_hide.animating()) {
136 			_hide.start(_update, 0., 1., _st.hideDuration);
137 		}
138 		_hiding = false;
139 	}
140 }
141 
finish()142 void RippleAnimation::Ripple::finish() {
143 	if (_update) {
144 		_update();
145 	}
146 	_show.stop();
147 	_hide.stop();
148 }
149 
clearCache()150 void RippleAnimation::Ripple::clearCache() {
151 	_cache = QPixmap();
152 }
153 
RippleAnimation(const style::RippleAnimation & st,QImage mask,Fn<void ()> callback)154 RippleAnimation::RippleAnimation(const style::RippleAnimation &st, QImage mask, Fn<void()> callback)
155 : _st(st)
156 , _mask(PixmapFromImage(std::move(mask)))
157 , _update(callback) {
158 }
159 
160 
add(QPoint origin,int startRadius)161 void RippleAnimation::add(QPoint origin, int startRadius) {
162 	lastStop();
163 	_ripples.push_back(std::make_unique<Ripple>(_st, origin, startRadius, _mask, _update));
164 }
165 
addFading()166 void RippleAnimation::addFading() {
167 	lastStop();
168 	_ripples.push_back(std::make_unique<Ripple>(_st, _mask, _update));
169 }
170 
lastStop()171 void RippleAnimation::lastStop() {
172 	if (!_ripples.empty()) {
173 		_ripples.back()->stop();
174 	}
175 }
176 
lastUnstop()177 void RippleAnimation::lastUnstop() {
178 	if (!_ripples.empty()) {
179 		_ripples.back()->unstop();
180 	}
181 }
182 
lastFinish()183 void RippleAnimation::lastFinish() {
184 	if (!_ripples.empty()) {
185 		_ripples.back()->finish();
186 	}
187 }
188 
forceRepaint()189 void RippleAnimation::forceRepaint() {
190 	for (const auto &ripple : _ripples) {
191 		ripple->clearCache();
192 	}
193 	if (_update) {
194 		_update();
195 	}
196 }
197 
paint(QPainter & p,int x,int y,int outerWidth,const QColor * colorOverride)198 void RippleAnimation::paint(QPainter &p, int x, int y, int outerWidth, const QColor *colorOverride) {
199 	if (_ripples.empty()) {
200 		return;
201 	}
202 
203 	if (style::RightToLeft()) {
204 		x = outerWidth - x - (_mask.width() / style::DevicePixelRatio());
205 	}
206 	p.translate(x, y);
207 	for (const auto &ripple : _ripples) {
208 		ripple->paint(p, _mask, colorOverride);
209 	}
210 	p.translate(-x, -y);
211 	clearFinished();
212 }
213 
maskByDrawer(QSize size,bool filled,Fn<void (QPainter & p)> drawer)214 QImage RippleAnimation::maskByDrawer(QSize size, bool filled, Fn<void(QPainter &p)> drawer) {
215 	auto result = QImage(size * style::DevicePixelRatio(), QImage::Format_ARGB32_Premultiplied);
216 	result.setDevicePixelRatio(style::DevicePixelRatio());
217 	result.fill(filled ? QColor(255, 255, 255) : Qt::transparent);
218 	if (drawer) {
219 		Painter p(&result);
220 		PainterHighQualityEnabler hq(p);
221 
222 		p.setPen(Qt::NoPen);
223 		p.setBrush(QColor(255, 255, 255));
224 		drawer(p);
225 	}
226 	return result;
227 }
228 
rectMask(QSize size)229 QImage RippleAnimation::rectMask(QSize size) {
230 	return maskByDrawer(size, true, Fn<void(QPainter&)>());
231 }
232 
roundRectMask(QSize size,int radius)233 QImage RippleAnimation::roundRectMask(QSize size, int radius) {
234 	return maskByDrawer(size, false, [size, radius](QPainter &p) {
235 		p.drawRoundedRect(0, 0, size.width(), size.height(), radius, radius);
236 	});
237 }
238 
ellipseMask(QSize size)239 QImage RippleAnimation::ellipseMask(QSize size) {
240 	return maskByDrawer(size, false, [size](QPainter &p) {
241 		p.drawEllipse(0, 0, size.width(), size.height());
242 	});
243 }
244 
clearFinished()245 void RippleAnimation::clearFinished() {
246 	while (!_ripples.empty() && _ripples.front()->finished()) {
247 		_ripples.pop_front();
248 	}
249 }
250 
clear()251 void RippleAnimation::clear() {
252 	_ripples.clear();
253 }
254 
255 RippleAnimation::~RippleAnimation() = default;
256 
257 } // namespace Ui
258