send_button.cpp 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "ui/controls/send_button.h"
  8. #include "lang/lang_tag.h"
  9. #include "ui/effects/ripple_animation.h"
  10. #include "ui/text/text_utilities.h"
  11. #include "ui/painter.h"
  12. #include "ui/ui_utility.h"
  13. #include "styles/style_boxes.h"
  14. #include "styles/style_chat_helpers.h"
  15. #include "styles/style_credits.h"
  16. namespace Ui {
  17. namespace {
  18. constexpr int kWideScale = 5;
  19. } // namespace
  20. SendButton::SendButton(QWidget *parent, const style::SendButton &st)
  21. : RippleButton(parent, st.inner.ripple)
  22. , _st(st) {
  23. updateSize();
  24. }
  25. void SendButton::setState(State state) {
  26. if (_state == state) {
  27. return;
  28. }
  29. const auto hasSlowmode = (_state.slowmodeDelay > 0);
  30. const auto hasSlowmodeChanged = hasSlowmode != (state.slowmodeDelay > 0);
  31. auto withSameSlowmode = state;
  32. withSameSlowmode.slowmodeDelay = _state.slowmodeDelay;
  33. const auto animate = hasSlowmodeChanged
  34. || (!hasSlowmode && withSameSlowmode != _state);
  35. if (animate) {
  36. _contentFrom = grabContent();
  37. }
  38. if (_state.slowmodeDelay != state.slowmodeDelay) {
  39. const auto seconds = state.slowmodeDelay;
  40. const auto minutes = seconds / 60;
  41. _slowmodeDelayText = seconds
  42. ? u"%1:%2"_q.arg(minutes).arg(seconds % 60, 2, 10, QChar('0'))
  43. : QString();
  44. }
  45. if (!state.starsToSend || state.type != Type::Send) {
  46. _starsToSendText = Text::String();
  47. } else if (_starsToSendText.isEmpty()
  48. || _state.starsToSend != state.starsToSend) {
  49. _starsToSendText.setMarkedText(
  50. _st.stars.style,
  51. Text::IconEmoji(&st::starIconEmoji).append(
  52. Lang::FormatCountToShort(state.starsToSend).string),
  53. kMarkupTextOptions);
  54. }
  55. _state = state;
  56. if (animate) {
  57. _stateChangeFromWidth = width();
  58. _stateChangeAnimation.stop();
  59. updateSize();
  60. _contentTo = grabContent();
  61. _stateChangeAnimation.start(
  62. [=] { updateSize(); update(); },
  63. 0.,
  64. 1.,
  65. st::universalDuration);
  66. setPointerCursor(_state.type != Type::Slowmode);
  67. updateSize();
  68. }
  69. update();
  70. }
  71. void SendButton::finishAnimating() {
  72. _stateChangeAnimation.stop();
  73. updateSize();
  74. update();
  75. }
  76. void SendButton::paintEvent(QPaintEvent *e) {
  77. auto p = QPainter(this);
  78. auto over = (isDown() || isOver());
  79. auto changed = _stateChangeAnimation.value(1.);
  80. if (changed < 1.) {
  81. PainterHighQualityEnabler hq(p);
  82. const auto ratio = style::DevicePixelRatio();
  83. p.setOpacity(1. - changed);
  84. const auto fromSize = _contentFrom.size() / (kWideScale * ratio);
  85. const auto fromShift = QPoint(
  86. (width() - fromSize.width()) / 2,
  87. (height() - fromSize.height()) / 2);
  88. auto fromRect = QRect(
  89. (1 - kWideScale) / 2 * fromSize.width(),
  90. (1 - kWideScale) / 2 * fromSize.height(),
  91. kWideScale * fromSize.width(),
  92. kWideScale * fromSize.height()
  93. ).translated(fromShift);
  94. auto hiddenWidth = anim::interpolate(0, (1 - kWideScale) / 2 * fromSize.width(), changed);
  95. auto hiddenHeight = anim::interpolate(0, (1 - kWideScale) / 2 * fromSize.height(), changed);
  96. p.drawPixmap(
  97. fromRect.marginsAdded(
  98. { hiddenWidth, hiddenHeight, hiddenWidth, hiddenHeight }),
  99. _contentFrom);
  100. p.setOpacity(changed);
  101. const auto toSize = _contentTo.size() / (kWideScale * ratio);
  102. const auto toShift = QPoint(
  103. (width() - toSize.width()) / 2,
  104. (height() - toSize.height()) / 2);
  105. auto toRect = QRect(
  106. (1 - kWideScale) / 2 * toSize.width(),
  107. (1 - kWideScale) / 2 * toSize.height(),
  108. kWideScale * toSize.width(),
  109. kWideScale * toSize.height()
  110. ).translated(toShift);
  111. auto shownWidth = anim::interpolate((1 - kWideScale) / 2 * width(), 0, changed);
  112. auto shownHeight = anim::interpolate((1 - kWideScale) / 2 * toSize.height(), 0, changed);
  113. p.drawPixmap(
  114. toRect.marginsAdded(
  115. { shownWidth, shownHeight, shownWidth, shownHeight }),
  116. _contentTo);
  117. return;
  118. }
  119. switch (_state.type) {
  120. case Type::Record: paintRecord(p, over); break;
  121. case Type::Round: paintRound(p, over); break;
  122. case Type::Save: paintSave(p, over); break;
  123. case Type::Cancel: paintCancel(p, over); break;
  124. case Type::Send:
  125. if (_starsToSendText.isEmpty()) {
  126. paintSend(p, over);
  127. } else {
  128. paintStarsToSend(p, over);
  129. }
  130. break;
  131. case Type::Schedule: paintSchedule(p, over); break;
  132. case Type::Slowmode: paintSlowmode(p); break;
  133. }
  134. }
  135. void SendButton::paintRecord(QPainter &p, bool over) {
  136. if (!isDisabled()) {
  137. paintRipple(
  138. p,
  139. (width() - _st.inner.rippleAreaSize) / 2,
  140. _st.inner.rippleAreaPosition.y());
  141. }
  142. const auto &icon = (isDisabled() || !over)
  143. ? _st.record
  144. : _st.recordOver;
  145. icon.paintInCenter(p, rect());
  146. }
  147. void SendButton::paintRound(QPainter &p, bool over) {
  148. if (!isDisabled()) {
  149. paintRipple(
  150. p,
  151. (width() - _st.inner.rippleAreaSize) / 2,
  152. _st.inner.rippleAreaPosition.y());
  153. }
  154. const auto &icon = (isDisabled() || !over)
  155. ? _st.round
  156. : _st.roundOver;
  157. icon.paintInCenter(p, rect());
  158. }
  159. void SendButton::paintSave(QPainter &p, bool over) {
  160. const auto &saveIcon = over
  161. ? st::historyEditSaveIconOver
  162. : st::historyEditSaveIcon;
  163. saveIcon.paintInCenter(p, rect());
  164. }
  165. void SendButton::paintCancel(QPainter &p, bool over) {
  166. paintRipple(
  167. p,
  168. (width() - _st.inner.rippleAreaSize) / 2,
  169. _st.inner.rippleAreaPosition.y());
  170. const auto &cancelIcon = over
  171. ? st::historyReplyCancelIconOver
  172. : st::historyReplyCancelIcon;
  173. cancelIcon.paintInCenter(p, rect());
  174. }
  175. void SendButton::paintSend(QPainter &p, bool over) {
  176. const auto &sendIcon = over ? _st.inner.iconOver : _st.inner.icon;
  177. if (isDisabled()) {
  178. const auto color = st::historyRecordVoiceFg->c;
  179. sendIcon.paint(p, st::historySendIconPosition, width(), color);
  180. } else {
  181. sendIcon.paint(p, st::historySendIconPosition, width());
  182. }
  183. }
  184. void SendButton::paintStarsToSend(QPainter &p, bool over) {
  185. const auto geometry = starsGeometry();
  186. {
  187. PainterHighQualityEnabler hq(p);
  188. p.setPen(Qt::NoPen);
  189. p.setBrush(over ? _st.stars.textBgOver : _st.stars.textBg);
  190. const auto radius = geometry.rounded.height() / 2;
  191. p.drawRoundedRect(geometry.rounded, radius, radius);
  192. }
  193. p.setPen(over ? _st.stars.textFgOver : _st.stars.textFg);
  194. _starsToSendText.draw(p, {
  195. .position = geometry.inner.topLeft(),
  196. .outerWidth = width(),
  197. .availableWidth = geometry.inner.width(),
  198. });
  199. }
  200. void SendButton::paintSchedule(QPainter &p, bool over) {
  201. {
  202. PainterHighQualityEnabler hq(p);
  203. p.setPen(Qt::NoPen);
  204. p.setBrush(over ? st::historySendIconFgOver : st::historySendIconFg);
  205. p.drawEllipse(
  206. st::historyScheduleIconPosition.x(),
  207. st::historyScheduleIconPosition.y(),
  208. st::historyScheduleIcon.width(),
  209. st::historyScheduleIcon.height());
  210. }
  211. st::historyScheduleIcon.paint(
  212. p,
  213. st::historyScheduleIconPosition,
  214. width());
  215. }
  216. void SendButton::paintSlowmode(QPainter &p) {
  217. p.setFont(st::normalFont);
  218. p.setPen(st::windowSubTextFg);
  219. p.drawText(
  220. rect().marginsRemoved(st::historySlowmodeCounterMargins),
  221. _slowmodeDelayText,
  222. style::al_center);
  223. }
  224. SendButton::StarsGeometry SendButton::starsGeometry() const {
  225. const auto &st = _st.stars;
  226. const auto inner = QRect(
  227. 0,
  228. 0,
  229. _starsToSendText.maxWidth(),
  230. st.style.font->height);
  231. const auto rounded = inner.marginsAdded(QMargins(
  232. st.padding.left() - st.width / 2,
  233. st.padding.top() + st.textTop,
  234. st.padding.right() - st.width / 2,
  235. st.height - st.padding.top() - st.textTop - st.style.font->height));
  236. const auto add = (_st.inner.height - rounded.height()) / 2;
  237. const auto outer = rounded.marginsAdded(QMargins(
  238. add,
  239. add,
  240. add,
  241. _st.inner.height - add - rounded.height()));
  242. const auto shift = -outer.topLeft();
  243. return {
  244. .inner = inner.translated(shift),
  245. .rounded = rounded.translated(shift),
  246. .outer = outer.translated(shift),
  247. };
  248. }
  249. void SendButton::updateSize() {
  250. const auto finalWidth = _starsToSendText.isEmpty()
  251. ? _st.inner.width
  252. : starsGeometry().outer.width();
  253. const auto progress = _stateChangeAnimation.value(1.);
  254. resize(
  255. anim::interpolate(_stateChangeFromWidth, finalWidth, progress),
  256. _st.inner.height);
  257. }
  258. QPixmap SendButton::grabContent() {
  259. auto result = QImage(
  260. kWideScale * size() * style::DevicePixelRatio(),
  261. QImage::Format_ARGB32_Premultiplied);
  262. result.setDevicePixelRatio(style::DevicePixelRatio());
  263. result.fill(Qt::transparent);
  264. {
  265. auto p = QPainter(&result);
  266. p.drawPixmap(
  267. (kWideScale - 1) / 2 * width(),
  268. (kWideScale - 1) / 2 * height(),
  269. GrabWidget(this));
  270. }
  271. return PixmapFromImage(std::move(result));
  272. }
  273. QImage SendButton::prepareRippleMask() const {
  274. const auto size = _st.inner.rippleAreaSize;
  275. return RippleAnimation::EllipseMask(QSize(size, size));
  276. }
  277. QPoint SendButton::prepareRippleStartPosition() const {
  278. const auto real = mapFromGlobal(QCursor::pos());
  279. const auto size = _st.inner.rippleAreaSize;
  280. const auto y = (height() - _st.inner.rippleAreaSize) / 2;
  281. return real - QPoint((width() - size) / 2, y);
  282. }
  283. } // namespace Ui