call_button.cpp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  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/widgets/call_button.h"
  8. #include "ui/effects/ripple_animation.h"
  9. #include "ui/painter.h"
  10. #include "ui/widgets/labels.h"
  11. #include "ui/qt_object_factory.h"
  12. #include "ui/ui_utility.h"
  13. #include "styles/style_widgets.h"
  14. #include "styles/palette.h"
  15. namespace Ui {
  16. namespace {
  17. constexpr auto kOuterBounceDuration = crl::time(100);
  18. } // namespace
  19. CallButton::CallButton(
  20. QWidget *parent,
  21. const style::CallButton &stFrom,
  22. const style::CallButton *stTo)
  23. : RippleButton(parent, stFrom.button.ripple)
  24. , _stFrom(&stFrom)
  25. , _stTo(stTo) {
  26. init();
  27. }
  28. void CallButton::init() {
  29. resize(_stFrom->button.width, _stFrom->button.height);
  30. const auto size = QSize(_stFrom->bgSize, _stFrom->bgSize);
  31. _bgMask = RippleAnimation::MaskByDrawer(size, false, [&](QPainter &p) {
  32. p.drawEllipse(0, 0, size.width(), size.height());
  33. if (_corner) {
  34. auto position = _corner->pos() - _stFrom->bgPosition;
  35. p.setCompositionMode(QPainter::CompositionMode_Source);
  36. p.setBrush(st::transparent);
  37. const auto border = _stFrom->cornerButtonBorder;
  38. p.drawEllipse(QRect(position, _corner->size()).marginsAdded(
  39. { border, border, border, border }));
  40. }
  41. });
  42. _bgFrom = Ui::PixmapFromImage(style::colorizeImage(_bgMask, _stFrom->bg));
  43. if (_stTo) {
  44. Assert(_stFrom->button.width == _stTo->button.width);
  45. Assert(_stFrom->button.height == _stTo->button.height);
  46. Assert(_stFrom->bgPosition == _stTo->bgPosition);
  47. Assert(_stFrom->bgSize == _stTo->bgSize);
  48. _bg = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
  49. _bg.setDevicePixelRatio(style::DevicePixelRatio());
  50. _bgTo = Ui::PixmapFromImage(style::colorizeImage(_bgMask, _stTo->bg));
  51. _iconMixedMask = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
  52. _iconMixedMask.setDevicePixelRatio(style::DevicePixelRatio());
  53. _iconFrom = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
  54. _iconFrom.setDevicePixelRatio(style::DevicePixelRatio());
  55. _iconFrom.fill(Qt::black);
  56. {
  57. QPainter p(&_iconFrom);
  58. p.drawImage(
  59. (_stFrom->bgSize
  60. - _stFrom->button.icon.width()) / 2,
  61. (_stFrom->bgSize
  62. - _stFrom->button.icon.height()) / 2,
  63. _stFrom->button.icon.instance(Qt::white));
  64. }
  65. _iconTo = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
  66. _iconTo.setDevicePixelRatio(style::DevicePixelRatio());
  67. _iconTo.fill(Qt::black);
  68. {
  69. QPainter p(&_iconTo);
  70. p.drawImage(
  71. (_stTo->bgSize
  72. - _stTo->button.icon.width()) / 2,
  73. (_stTo->bgSize
  74. - _stTo->button.icon.height()) / 2,
  75. _stTo->button.icon.instance(Qt::white));
  76. }
  77. _iconMixed = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied);
  78. _iconMixed.setDevicePixelRatio(style::DevicePixelRatio());
  79. }
  80. }
  81. void CallButton::setOuterValue(float64 value) {
  82. if (_outerValue != value) {
  83. _outerAnimation.start([this] {
  84. if (_progress == 0. || _progress == 1.) {
  85. update();
  86. }
  87. }, _outerValue, value, kOuterBounceDuration);
  88. _outerValue = value;
  89. }
  90. }
  91. void CallButton::setText(rpl::producer<QString> text) {
  92. _label.create(this, std::move(text), _stFrom->label);
  93. _label->show();
  94. rpl::combine(
  95. sizeValue(),
  96. _label->sizeValue()
  97. ) | rpl::start_with_next([=](QSize my, QSize label) {
  98. _label->moveToLeft(
  99. (my.width() - label.width()) / 2,
  100. my.height() - label.height(),
  101. my.width());
  102. }, _label->lifetime());
  103. }
  104. void CallButton::setProgress(float64 progress) {
  105. _progress = progress;
  106. if (_corner) {
  107. _corner->setProgress(progress);
  108. }
  109. update();
  110. }
  111. void CallButton::paintEvent(QPaintEvent *e) {
  112. QPainter p(this);
  113. auto bgPosition = myrtlpoint(_stFrom->bgPosition);
  114. auto paintFrom = (_progress == 0.) || !_stTo;
  115. auto paintTo = !paintFrom && (_progress == 1.);
  116. auto outerValue = _outerAnimation.value(_outerValue);
  117. if (outerValue > 0.) {
  118. auto outerRadius = paintFrom ? _stFrom->outerRadius : paintTo ? _stTo->outerRadius : (_stFrom->outerRadius * (1. - _progress) + _stTo->outerRadius * _progress);
  119. auto outerPixels = outerValue * outerRadius;
  120. auto outerRect = QRectF(myrtlrect(bgPosition.x(), bgPosition.y(), _stFrom->bgSize, _stFrom->bgSize));
  121. outerRect = outerRect.marginsAdded(QMarginsF(outerPixels, outerPixels, outerPixels, outerPixels));
  122. PainterHighQualityEnabler hq(p);
  123. if (paintFrom) {
  124. p.setBrush(_stFrom->outerBg);
  125. } else if (paintTo) {
  126. p.setBrush(_stTo->outerBg);
  127. } else {
  128. p.setBrush(anim::brush(_stFrom->outerBg, _stTo->outerBg, _progress));
  129. }
  130. p.setPen(Qt::NoPen);
  131. p.drawEllipse(outerRect);
  132. }
  133. if (_bgOverride) {
  134. Assert(!_corner); // Didn't support this case yet.
  135. const auto &s = _stFrom->bgSize;
  136. p.setPen(Qt::NoPen);
  137. p.setBrush(*_bgOverride);
  138. PainterHighQualityEnabler hq(p);
  139. p.drawEllipse(QRect(_stFrom->bgPosition, QSize(s, s)));
  140. } else if (paintFrom) {
  141. p.drawPixmap(bgPosition, _bgFrom);
  142. } else if (paintTo) {
  143. p.drawPixmap(bgPosition, _bgTo);
  144. } else {
  145. style::colorizeImage(_bgMask, anim::color(_stFrom->bg, _stTo->bg, _progress), &_bg);
  146. p.drawImage(bgPosition, _bg);
  147. }
  148. auto rippleColorInterpolated = QColor();
  149. auto rippleColorOverride = &rippleColorInterpolated;
  150. if (_rippleOverride) {
  151. rippleColorOverride = &(*_rippleOverride);
  152. } else if (paintFrom) {
  153. rippleColorOverride = nullptr;
  154. } else if (paintTo) {
  155. rippleColorOverride = &_stTo->button.ripple.color->c;
  156. } else {
  157. rippleColorInterpolated = anim::color(_stFrom->button.ripple.color, _stTo->button.ripple.color, _progress);
  158. }
  159. paintRipple(p, _stFrom->button.rippleAreaPosition, rippleColorOverride);
  160. auto positionFrom = iconPosition(_stFrom);
  161. if (paintFrom) {
  162. const auto icon = &_stFrom->button.icon;
  163. icon->paint(p, positionFrom, width());
  164. } else {
  165. auto positionTo = iconPosition(_stTo);
  166. if (paintTo) {
  167. _stTo->button.icon.paint(p, positionTo, width());
  168. } else {
  169. mixIconMasks();
  170. style::colorizeImage(_iconMixedMask, st::callIconFg->c, &_iconMixed);
  171. p.drawImage(myrtlpoint(_stFrom->bgPosition), _iconMixed);
  172. }
  173. }
  174. }
  175. QPoint CallButton::iconPosition(not_null<const style::CallButton*> st) const {
  176. auto result = st->button.iconPosition;
  177. if (result.x() < 0) {
  178. result.setX((width() - st->button.icon.width()) / 2);
  179. }
  180. if (result.y() < 0) {
  181. result.setY((height() - st->button.icon.height()) / 2);
  182. }
  183. return result;
  184. }
  185. void CallButton::mixIconMasks() {
  186. _iconMixedMask.fill(Qt::black);
  187. Painter p(&_iconMixedMask);
  188. PainterHighQualityEnabler hq(p);
  189. auto paintIconMask = [this, &p](const QImage &mask, float64 angle) {
  190. auto skipFrom = _stFrom->bgSize / 2;
  191. p.translate(skipFrom, skipFrom);
  192. p.rotate(angle);
  193. p.translate(-skipFrom, -skipFrom);
  194. p.drawImage(0, 0, mask);
  195. };
  196. p.save();
  197. paintIconMask(_iconFrom, (_stFrom->angle - _stTo->angle) * _progress);
  198. p.restore();
  199. p.setOpacity(_progress);
  200. paintIconMask(_iconTo, (_stTo->angle - _stFrom->angle) * (1. - _progress));
  201. }
  202. void CallButton::onStateChanged(State was, StateChangeSource source) {
  203. RippleButton::onStateChanged(was, source);
  204. auto over = isOver();
  205. auto wasOver = static_cast<bool>(was & StateFlag::Over);
  206. if (over != wasOver) {
  207. update();
  208. }
  209. }
  210. void CallButton::setColorOverrides(rpl::producer<CallButtonColors> &&colors) {
  211. std::move(
  212. colors
  213. ) | rpl::start_with_next([=](const CallButtonColors &c) {
  214. _bgOverride = c.bg;
  215. _rippleOverride = c.ripple;
  216. update();
  217. }, lifetime());
  218. }
  219. void CallButton::setStyle(
  220. const style::CallButton &stFrom,
  221. const style::CallButton *stTo) {
  222. if (_stFrom == &stFrom && _stTo == stTo) {
  223. return;
  224. }
  225. _stFrom = &stFrom;
  226. _stTo = stTo;
  227. init();
  228. update();
  229. }
  230. not_null<CallButton*> CallButton::addCornerButton(
  231. const style::CallButton &stFrom,
  232. const style::CallButton *stTo) {
  233. Expects(!_corner);
  234. _corner = CreateChild<CallButton>(this, stFrom, stTo);
  235. _corner->move(_stFrom->cornerButtonPosition);
  236. _corner->setProgress(_progress);
  237. _corner->show();
  238. init();
  239. update();
  240. return _corner;
  241. }
  242. QPoint CallButton::prepareRippleStartPosition() const {
  243. return mapFromGlobal(QCursor::pos()) - _stFrom->button.rippleAreaPosition;
  244. }
  245. QImage CallButton::prepareRippleMask() const {
  246. return _bgMask;
  247. }
  248. } // namespace Ui