chat_service_checkbox.cpp 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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/chat_service_checkbox.h"
  8. #include "ui/widgets/checkbox.h"
  9. #include "ui/painter.h"
  10. #include "styles/style_layers.h"
  11. #include <QCoreApplication>
  12. namespace Ui {
  13. namespace {
  14. constexpr auto kAnimationTimerDelta = crl::time(7);
  15. class ServiceCheck final : public AbstractCheckView {
  16. public:
  17. ServiceCheck(const style::ServiceCheck &st, bool checked);
  18. QSize getSize() const override;
  19. void paint(
  20. QPainter &p,
  21. int left,
  22. int top,
  23. int outerWidth) override;
  24. QImage prepareRippleMask() const override;
  25. bool checkRippleStartPosition(QPoint position) const override;
  26. private:
  27. class Generator {
  28. public:
  29. Generator();
  30. void paintFrame(
  31. QPainter &p,
  32. int left,
  33. int top,
  34. not_null<const style::ServiceCheck*> st,
  35. float64 toggled);
  36. void invalidate();
  37. private:
  38. struct Frames {
  39. QImage image;
  40. std::vector<bool> ready;
  41. };
  42. not_null<Frames*> framesForStyle(
  43. not_null<const style::ServiceCheck*> st);
  44. static void FillFrame(
  45. QImage &image,
  46. not_null<const style::ServiceCheck*> st,
  47. int index,
  48. int count);
  49. static void PaintFillingFrame(
  50. QPainter &p,
  51. not_null<const style::ServiceCheck*> st,
  52. float64 progress);
  53. static void PaintCheckingFrame(
  54. QPainter &p,
  55. not_null<const style::ServiceCheck*> st,
  56. float64 progress);
  57. base::flat_map<not_null<const style::ServiceCheck*>, Frames> _data;
  58. rpl::lifetime _lifetime;
  59. };
  60. static Generator &Frames();
  61. const style::ServiceCheck &_st;
  62. };
  63. ServiceCheck::Generator::Generator() {
  64. style::PaletteChanged(
  65. ) | rpl::start_with_next([=] {
  66. invalidate();
  67. }, _lifetime);
  68. }
  69. auto ServiceCheck::Generator::framesForStyle(
  70. not_null<const style::ServiceCheck*> st) -> not_null<Frames*> {
  71. if (const auto i = _data.find(st); i != _data.end()) {
  72. return &i->second;
  73. }
  74. const auto result = &_data.emplace(st, Frames()).first->second;
  75. const auto size = st->diameter;
  76. const auto count = (st->duration / kAnimationTimerDelta) + 2;
  77. result->image = QImage(
  78. QSize(count * size, size) * style::DevicePixelRatio(),
  79. QImage::Format_ARGB32_Premultiplied);
  80. result->image.fill(Qt::transparent);
  81. result->image.setDevicePixelRatio(style::DevicePixelRatio());
  82. result->ready.resize(count);
  83. return result;
  84. }
  85. void ServiceCheck::Generator::FillFrame(
  86. QImage &image,
  87. not_null<const style::ServiceCheck*> st,
  88. int index,
  89. int count) {
  90. Expects(count > 1);
  91. Expects(index >= 0 && index < count);
  92. auto p = QPainter(&image);
  93. PainterHighQualityEnabler hq(p);
  94. p.translate(index * st->diameter, 0);
  95. const auto progress = index / float64(count - 1);
  96. if (progress > 0.5) {
  97. PaintCheckingFrame(p, st, (progress - 0.5) * 2);
  98. } else {
  99. PaintFillingFrame(p, st, progress * 2);
  100. }
  101. }
  102. void ServiceCheck::Generator::PaintFillingFrame(
  103. QPainter &p,
  104. not_null<const style::ServiceCheck*> st,
  105. float64 progress) {
  106. const auto shift = progress * st->shift;
  107. p.setBrush(st->color);
  108. p.setPen(Qt::NoPen);
  109. p.drawEllipse(QRectF(
  110. shift,
  111. shift,
  112. st->diameter - 2 * shift,
  113. st->diameter - 2 * shift));
  114. if (progress < 1.) {
  115. const auto remove = progress * (st->diameter / 2. - st->thickness);
  116. p.setCompositionMode(QPainter::CompositionMode_Source);
  117. p.setPen(Qt::NoPen);
  118. p.setBrush(Qt::transparent);
  119. p.drawEllipse(QRectF(
  120. st->thickness + remove,
  121. st->thickness + remove,
  122. st->diameter - 2 * (st->thickness + remove),
  123. st->diameter - 2 * (st->thickness + remove)));
  124. }
  125. }
  126. void ServiceCheck::Generator::PaintCheckingFrame(
  127. QPainter &p,
  128. not_null<const style::ServiceCheck*> st,
  129. float64 progress) {
  130. const auto shift = (1. - progress) * st->shift;
  131. p.setBrush(st->color);
  132. p.setPen(Qt::NoPen);
  133. p.drawEllipse(QRectF(
  134. shift,
  135. shift,
  136. st->diameter - 2 * shift,
  137. st->diameter - 2 * shift));
  138. if (progress > 0.) {
  139. const auto tip = QPointF(st->tip.x(), st->tip.y());
  140. const auto left = tip - QPointF(st->small, st->small) * progress;
  141. const auto right = tip - QPointF(-st->large, st->large) * progress;
  142. p.setCompositionMode(QPainter::CompositionMode_Source);
  143. p.setBrush(Qt::NoBrush);
  144. auto pen = QPen(Qt::transparent);
  145. pen.setWidth(st->stroke);
  146. pen.setCapStyle(Qt::RoundCap);
  147. pen.setJoinStyle(Qt::RoundJoin);
  148. p.setPen(pen);
  149. auto path = QPainterPath();
  150. path.moveTo(left);
  151. path.lineTo(tip);
  152. path.lineTo(right);
  153. p.drawPath(path);
  154. }
  155. }
  156. void ServiceCheck::Generator::paintFrame(
  157. QPainter &p,
  158. int left,
  159. int top,
  160. not_null<const style::ServiceCheck*> st,
  161. float64 toggled) {
  162. const auto frames = framesForStyle(st);
  163. auto &image = frames->image;
  164. const auto count = int(frames->ready.size());
  165. const auto index = int(base::SafeRound(toggled * (count - 1)));
  166. Assert(index >= 0 && index < count);
  167. if (!frames->ready[index]) {
  168. frames->ready[index] = true;
  169. FillFrame(image, st, index, count);
  170. }
  171. const auto size = st->diameter;
  172. const auto part = size * style::DevicePixelRatio();
  173. p.drawImage(
  174. QPoint(left, top),
  175. image,
  176. QRect(index * part, 0, part, part));
  177. }
  178. void ServiceCheck::Generator::invalidate() {
  179. _data.clear();
  180. }
  181. ServiceCheck::Generator &ServiceCheck::Frames() {
  182. static const auto Instance = Ui::CreateChild<Generator>(
  183. QCoreApplication::instance());
  184. return *Instance;
  185. }
  186. ServiceCheck::ServiceCheck(
  187. const style::ServiceCheck &st,
  188. bool checked)
  189. : AbstractCheckView(st.duration, checked, nullptr)
  190. , _st(st) {
  191. }
  192. QSize ServiceCheck::getSize() const {
  193. const auto inner = QRect(0, 0, _st.diameter, _st.diameter);
  194. return inner.marginsAdded(_st.margin).size();
  195. }
  196. void ServiceCheck::paint(
  197. QPainter &p,
  198. int left,
  199. int top,
  200. int outerWidth) {
  201. Frames().paintFrame(
  202. p,
  203. left + _st.margin.left(),
  204. top + _st.margin.top(),
  205. &_st,
  206. currentAnimationValue());
  207. }
  208. QImage ServiceCheck::prepareRippleMask() const {
  209. return QImage();
  210. }
  211. bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
  212. return false;
  213. }
  214. void SetupBackground(not_null<Checkbox*> checkbox, Fn<QColor()> bg) {
  215. checkbox->paintRequest(
  216. ) | rpl::map(
  217. bg ? bg : [] { return st::msgServiceBg->c; }
  218. ) | rpl::filter([=](const QColor &color) {
  219. return color.alpha() > 0;
  220. }) | rpl::start_with_next([=](const QColor &color) {
  221. auto p = QPainter(checkbox);
  222. PainterHighQualityEnabler hq(p);
  223. p.setPen(Qt::NoPen);
  224. p.setBrush(color);
  225. const auto radius = checkbox->height() / 2.;
  226. p.drawRoundedRect(checkbox->rect(), radius, radius);
  227. }, checkbox->lifetime());
  228. }
  229. } // namespace
  230. [[nodiscard]] object_ptr<Checkbox> MakeChatServiceCheckbox(
  231. QWidget *parent,
  232. const QString &text,
  233. const style::Checkbox &st,
  234. const style::ServiceCheck &stCheck,
  235. bool checked,
  236. Fn<QColor()> bg) {
  237. auto result = object_ptr<Checkbox>(
  238. parent,
  239. text,
  240. st,
  241. std::make_unique<ServiceCheck>(stCheck, checked));
  242. SetupBackground(result.data(), std::move(bg));
  243. return result;
  244. }
  245. } // namespace Ui