ripple_animation.cpp 7.4 KB

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