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