loading_element.cpp 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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/effects/loading_element.h"
  8. #include "base/object_ptr.h"
  9. #include "base/random.h"
  10. #include "styles/palette.h"
  11. #include "ui/effects/glare.h"
  12. #include "ui/painter.h"
  13. #include "ui/rect.h"
  14. #include "ui/rp_widget.h"
  15. #include "styles/style_basic.h"
  16. #include "styles/style_dialogs.h"
  17. #include "styles/style_widgets.h"
  18. namespace Ui {
  19. namespace {
  20. class LoadingElement {
  21. public:
  22. LoadingElement() = default;
  23. [[nodiscard]] virtual int height() const = 0;
  24. virtual void paint(QPainter &p, int width) = 0;
  25. };
  26. class LoadingText final : public LoadingElement {
  27. public:
  28. LoadingText(const style::FlatLabel &st);
  29. [[nodiscard]] int height() const override;
  30. void paint(QPainter &p, int width) override;
  31. private:
  32. const style::FlatLabel &_st;
  33. };
  34. LoadingText::LoadingText(const style::FlatLabel &st) : _st(st) {
  35. }
  36. int LoadingText::height() const {
  37. return _st.style.lineHeight;
  38. }
  39. void LoadingText::paint(QPainter &p, int width) {
  40. auto hq = PainterHighQualityEnabler(p);
  41. p.setPen(Qt::NoPen);
  42. p.setBrush(st::windowBgOver);
  43. const auto h = _st.style.font->ascent;
  44. p.drawRoundedRect(
  45. 0,
  46. height() - h - (height() - _st.style.font->height),
  47. width,
  48. h,
  49. h / 2,
  50. h / 2);
  51. }
  52. [[nodiscard]] const style::PeerListItem &PeerListItemFromDialogRow(
  53. rpl::lifetime &lifetime,
  54. const style::DialogRow &st) {
  55. using namespace style;
  56. const auto item = lifetime.make_state<PeerListItem>(PeerListItem{
  57. .height = st.height,
  58. .photoPosition = QPoint(st.padding.left(), st.padding.top()),
  59. .namePosition = QPoint(st.nameLeft, st.nameTop),
  60. .nameStyle = st::semiboldTextStyle,
  61. .statusPosition = QPoint(st.textLeft, st.textTop),
  62. .photoSize = st.photoSize,
  63. });
  64. return *item;
  65. }
  66. class LoadingPeerListItem final : public LoadingElement {
  67. public:
  68. LoadingPeerListItem(const style::PeerListItem &st) : _st(st) {
  69. }
  70. LoadingPeerListItem(const style::DialogRow &st)
  71. : _st(PeerListItemFromDialogRow(_lifetime, st)) {
  72. }
  73. [[nodiscard]] int height() const override {
  74. return _st.height;
  75. }
  76. void paint(QPainter &p, int width) override {
  77. auto hq = PainterHighQualityEnabler(p);
  78. const auto &style = _st.nameStyle;
  79. const auto offset = -style.font->ascent
  80. - (style.lineHeight - style.font->height);
  81. p.setPen(Qt::NoPen);
  82. p.setBrush(st::windowBgOver);
  83. p.drawEllipse(
  84. _st.photoPosition.x(),
  85. _st.photoPosition.y(),
  86. _st.photoSize,
  87. _st.photoSize);
  88. constexpr auto kNameWidth = 60;
  89. constexpr auto kStatusWidth = 100;
  90. const auto h1 = st::semiboldTextStyle.font->ascent;
  91. p.drawRoundedRect(
  92. _st.namePosition.x(),
  93. _st.namePosition.y() + offset,
  94. kNameWidth,
  95. h1,
  96. h1 / 2,
  97. h1 / 2);
  98. {
  99. const auto h2 = st::defaultTextStyle.font->ascent;
  100. const auto radius = h2 / 2;
  101. const auto rect = QRect(
  102. _st.statusPosition.x(),
  103. _st.statusPosition.y() + offset,
  104. kStatusWidth,
  105. h2);
  106. if (rect::bottom(rect) < height()) {
  107. p.drawRoundedRect(rect, radius, radius);
  108. }
  109. }
  110. }
  111. private:
  112. rpl::lifetime _lifetime;
  113. const style::PeerListItem &_st;
  114. };
  115. template <typename Element, typename ...ElementArgs>
  116. object_ptr<Ui::RpWidget> CreateLoadingElementWidget(
  117. not_null<Ui::RpWidget*> parent,
  118. int lines,
  119. rpl::producer<bool> rtl,
  120. ElementArgs &&...args) {
  121. auto widget = object_ptr<Ui::RpWidget>(parent);
  122. const auto raw = widget.data();
  123. struct State {
  124. GlareEffect glare;
  125. Ui::Animations::Simple animation;
  126. int lastLineWidth = 0;
  127. rpl::variable<bool> rtl;
  128. };
  129. const auto state = widget->lifetime().make_state<State>();
  130. state->rtl = std::move(rtl);
  131. state->rtl.value(
  132. ) | rpl::start_with_next([=] { raw->update(); }, raw->lifetime());
  133. raw->resize(
  134. raw->width(),
  135. Element(std::forward<ElementArgs>(args)...).height() * lines);
  136. const auto draw = [=](QPainter &p) {
  137. auto loading = Element(std::forward<ElementArgs>(args)...);
  138. const auto countRows = lines;
  139. for (auto i = 0; i < countRows; i++) {
  140. const auto w = (i == countRows - 1)
  141. ? state->lastLineWidth
  142. : raw->width();
  143. loading.paint(p, w);
  144. p.translate(0, loading.height());
  145. }
  146. p.resetTransform();
  147. auto &_glare = state->glare;
  148. if (_glare.glare.birthTime) {
  149. const auto progress = _glare.progress(crl::now());
  150. const auto x = (-_glare.width)
  151. + (raw->width() + _glare.width * 2) * progress;
  152. const auto h = raw->height();
  153. p.drawTiledPixmap(x, 0, _glare.width, h, _glare.pixmap, 0, 0);
  154. }
  155. };
  156. widget->paintRequest(
  157. ) | rpl::start_with_next([=](const QRect &r) {
  158. auto p = QPainter(raw);
  159. if (state->rtl.current()) {
  160. const auto r = raw->rect();
  161. p.translate(r.center());
  162. p.scale(-1., 1.);
  163. p.translate(-r.center());
  164. }
  165. draw(p);
  166. }, widget->lifetime());
  167. constexpr auto kTimeout = crl::time(1000);
  168. constexpr auto kDuration = crl::time(1000);
  169. widget->widthValue(
  170. ) | rpl::start_with_next([=](int width) {
  171. state->glare.width = width;
  172. state->glare.validate(
  173. st::dialogsBg->c,
  174. [=] { raw->update(); },
  175. kTimeout,
  176. kDuration);
  177. if (width) {
  178. state->lastLineWidth = (width / 4) + base::RandomIndex(width / 2);
  179. }
  180. }, widget->lifetime());
  181. return widget;
  182. }
  183. } // namespace
  184. object_ptr<Ui::RpWidget> CreateLoadingTextWidget(
  185. not_null<Ui::RpWidget*> parent,
  186. const style::FlatLabel &st,
  187. int lines,
  188. rpl::producer<bool> rtl) {
  189. return CreateLoadingElementWidget<LoadingText>(
  190. parent,
  191. lines,
  192. std::move(rtl),
  193. st);
  194. }
  195. object_ptr<Ui::RpWidget> CreateLoadingPeerListItemWidget(
  196. not_null<Ui::RpWidget*> parent,
  197. const style::PeerListItem &st,
  198. int lines) {
  199. return CreateLoadingElementWidget<LoadingPeerListItem>(
  200. parent,
  201. lines,
  202. rpl::single(false),
  203. st);
  204. }
  205. object_ptr<Ui::RpWidget> CreateLoadingDialogRowWidget(
  206. not_null<Ui::RpWidget*> parent,
  207. const style::DialogRow &st,
  208. int lines) {
  209. return CreateLoadingElementWidget<LoadingPeerListItem>(
  210. parent,
  211. lines,
  212. rpl::single(false),
  213. st);
  214. }
  215. } // namespace Ui