numbers_animation.cpp 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  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/numbers_animation.h"
  8. #include "ui/painter.h"
  9. #include "styles/style_widgets.h"
  10. #include <QtGui/QPainter>
  11. namespace Ui {
  12. NumbersAnimation::NumbersAnimation(
  13. const style::font &font,
  14. Fn<void()> animationCallback)
  15. : _font(font)
  16. , _duration(st::slideWrapDuration)
  17. , _animationCallback(std::move(animationCallback)) {
  18. for (auto ch = '0'; ch != '9'; ++ch) {
  19. accumulate_max(_digitWidth, _font->width(ch));
  20. }
  21. }
  22. void NumbersAnimation::setDuration(int duration) {
  23. _duration = duration;
  24. }
  25. void NumbersAnimation::setDisabledMonospace(bool value) {
  26. _disabledMonospace = value;
  27. }
  28. void NumbersAnimation::setText(const QString &text, int value) {
  29. if (_a_ready.animating()) {
  30. _delayedText = text;
  31. _delayedValue = value;
  32. } else {
  33. realSetText(text, value);
  34. }
  35. }
  36. void NumbersAnimation::animationCallback() {
  37. if (_animationCallback) {
  38. _animationCallback();
  39. }
  40. if (_widthChangedCallback) {
  41. _widthChangedCallback();
  42. }
  43. if (!_a_ready.animating()) {
  44. if (!_delayedText.isEmpty()) {
  45. setText(_delayedText, _delayedValue);
  46. }
  47. }
  48. }
  49. void NumbersAnimation::realSetText(QString text, int value) {
  50. _delayedText = QString();
  51. _delayedValue = 0;
  52. _growing = (value > _value);
  53. _value = value;
  54. auto newSize = text.size();
  55. while (_digits.size() < newSize) {
  56. _digits.push_front(Digit());
  57. }
  58. while (_digits.size() > newSize && !_digits.front().to.unicode()) {
  59. _digits.pop_front();
  60. }
  61. auto animating = false;
  62. auto toFullWidth = 0;
  63. auto bothFullWidth = 0;
  64. for (auto i = 0, size = int(_digits.size()); i != size; ++i) {
  65. auto &digit = _digits[i];
  66. const auto from = digit.from = digit.to;
  67. digit.fromWidth = digit.toWidth;
  68. const auto to = digit.to = (newSize + i < size)
  69. ? QChar(0)
  70. : text[newSize + i - size];
  71. digit.toWidth = to.unicode() ? _font->width(to) : 0;
  72. if (from != to) {
  73. animating = true;
  74. }
  75. const auto toCharWidth = (!_disabledMonospace || to.isDigit())
  76. ? _digitWidth
  77. : digit.toWidth;
  78. const auto fromCharWidth = (!_disabledMonospace || from.isDigit())
  79. ? _digitWidth
  80. : digit.fromWidth;
  81. const auto charWidth = std::max(toCharWidth, fromCharWidth);
  82. bothFullWidth += charWidth;
  83. if (to.unicode()) {
  84. toFullWidth += charWidth;
  85. }
  86. }
  87. _fromWidth = _toWidth;
  88. _toWidth = toFullWidth;
  89. _bothWidth = bothFullWidth;
  90. if (animating) {
  91. _a_ready.start(
  92. [this] { animationCallback(); },
  93. 0.,
  94. 1.,
  95. _duration);
  96. }
  97. }
  98. int NumbersAnimation::countWidth() const {
  99. return anim::interpolate(
  100. _fromWidth,
  101. _toWidth,
  102. anim::easeOutCirc(1., _a_ready.value(1.)));
  103. }
  104. int NumbersAnimation::maxWidth() const {
  105. return std::max(_fromWidth, _toWidth);
  106. }
  107. void NumbersAnimation::finishAnimating() {
  108. auto width = countWidth();
  109. _a_ready.stop();
  110. if (_widthChangedCallback && countWidth() != width) {
  111. _widthChangedCallback();
  112. }
  113. if (!_delayedText.isEmpty()) {
  114. setText(_delayedText, _delayedValue);
  115. }
  116. }
  117. void NumbersAnimation::paint(QPainter &p, int x, int y, int outerWidth) {
  118. auto digitsCount = _digits.size();
  119. if (!digitsCount) return;
  120. auto progress = anim::easeOutCirc(1., _a_ready.value(1.));
  121. auto width = anim::interpolate(_fromWidth, _toWidth, progress);
  122. QString singleChar('0');
  123. if (style::RightToLeft()) x = outerWidth - x - width;
  124. x += width - _bothWidth;
  125. auto fromTop = anim::interpolate(0, _font->height, progress) * (_growing ? 1 : -1);
  126. auto toTop = anim::interpolate(_font->height, 0, progress) * (_growing ? -1 : 1);
  127. for (auto i = 0; i != digitsCount; ++i) {
  128. auto &digit = _digits[i];
  129. auto from = digit.from;
  130. auto to = digit.to;
  131. const auto toCharWidth = (!_disabledMonospace || to.isDigit())
  132. ? _digitWidth
  133. : digit.toWidth;
  134. const auto fromCharWidth = (!_disabledMonospace || from.isDigit())
  135. ? _digitWidth
  136. : digit.fromWidth;
  137. if (from == to) {
  138. p.setOpacity(1.);
  139. singleChar[0] = from;
  140. p.drawText(x + (toCharWidth - digit.fromWidth) / 2, y + _font->ascent, singleChar);
  141. } else {
  142. if (from.unicode()) {
  143. p.setOpacity(1. - progress);
  144. singleChar[0] = from;
  145. p.drawText(x + (fromCharWidth - digit.fromWidth) / 2, y + fromTop + _font->ascent, singleChar);
  146. }
  147. if (to.unicode()) {
  148. p.setOpacity(progress);
  149. singleChar[0] = to;
  150. p.drawText(x + (toCharWidth - digit.toWidth) / 2, y + toTop + _font->ascent, singleChar);
  151. }
  152. }
  153. x += std::max(toCharWidth, fromCharWidth);
  154. }
  155. p.setOpacity(1.);
  156. }
  157. LabelWithNumbers::LabelWithNumbers(
  158. QWidget *parent,
  159. const style::FlatLabel &st,
  160. int textTop,
  161. const StringWithNumbers &value)
  162. : RpWidget(parent)
  163. , _st(st)
  164. , _textTop(textTop)
  165. , _before(GetBefore(value))
  166. , _after(GetAfter(value))
  167. , _numbers(_st.style.font, [=] { update(); })
  168. , _beforeWidth(_st.style.font->width(_before))
  169. , _afterWidth(st.style.font->width(_after)) {
  170. Expects((value.offset < 0) == (value.length == 0));
  171. const auto numbers = GetNumbers(value);
  172. _numbers.setText(numbers, numbers.toInt());
  173. _numbers.finishAnimating();
  174. }
  175. QString LabelWithNumbers::GetBefore(const StringWithNumbers &value) {
  176. return value.text.mid(0, value.offset);
  177. }
  178. QString LabelWithNumbers::GetAfter(const StringWithNumbers &value) {
  179. return (value.offset >= 0)
  180. ? value.text.mid(value.offset + value.length)
  181. : QString();
  182. }
  183. QString LabelWithNumbers::GetNumbers(const StringWithNumbers &value) {
  184. return (value.offset >= 0)
  185. ? value.text.mid(value.offset, value.length)
  186. : QString();
  187. }
  188. void LabelWithNumbers::setValue(const StringWithNumbers &value) {
  189. _before = GetBefore(value);
  190. _after = GetAfter(value);
  191. const auto numbers = GetNumbers(value);
  192. _numbers.setText(numbers, numbers.toInt());
  193. const auto oldBeforeWidth = std::exchange(
  194. _beforeWidth,
  195. _st.style.font->width(_before));
  196. _beforeWidthAnimation.start(
  197. [this] { update(); },
  198. oldBeforeWidth,
  199. _beforeWidth,
  200. st::slideWrapDuration,
  201. anim::easeOutCirc);
  202. _afterWidth = _st.style.font->width(_after);
  203. }
  204. void LabelWithNumbers::finishAnimating() {
  205. _beforeWidthAnimation.stop();
  206. _numbers.finishAnimating();
  207. update();
  208. }
  209. void LabelWithNumbers::paintEvent(QPaintEvent *e) {
  210. Painter p(this);
  211. const auto beforeWidth = _beforeWidthAnimation.value(_beforeWidth);
  212. p.setFont(_st.style.font);
  213. p.setBrush(Qt::NoBrush);
  214. p.setPen(_st.textFg);
  215. auto left = 0;
  216. const auto outerWidth = width();
  217. p.setClipRect(0, 0, left + beforeWidth, height());
  218. p.drawTextLeft(left, _textTop, outerWidth, _before, _beforeWidth);
  219. left += beforeWidth;
  220. p.setClipping(false);
  221. _numbers.paint(p, left, _textTop, outerWidth);
  222. left += _numbers.countWidth();
  223. const auto availableWidth = outerWidth - left;
  224. const auto text = (availableWidth < _afterWidth)
  225. ? _st.style.font->elided(_after, availableWidth)
  226. : _after;
  227. const auto textWidth = (availableWidth < _afterWidth) ? -1 : _afterWidth;
  228. p.drawTextLeft(left, _textTop, outerWidth, text, textWidth);
  229. }
  230. } // namespace Ui