toast_widget.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  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/toast/toast_widget.h"
  8. #include "ui/image/image_prepare.h"
  9. #include "ui/text/text_utilities.h"
  10. #include "ui/widgets/labels.h"
  11. #include "ui/widgets/tooltip.h"
  12. #include "ui/painter.h"
  13. #include "ui/ui_utility.h"
  14. #include "styles/palette.h"
  15. #include "styles/style_widgets.h"
  16. #include <QtGui/QtEvents>
  17. namespace Ui::Toast::internal {
  18. namespace {
  19. [[nodiscard]] TextWithEntities ComputeText(const Config &config) {
  20. auto result = config.text;
  21. if (!config.title.isEmpty()) {
  22. result = Text::Bold(
  23. config.title
  24. ).append('\n').append(std::move(result));
  25. }
  26. return config.singleline
  27. ? TextUtilities::SingleLine(std::move(result))
  28. : result;
  29. }
  30. [[nodiscard]] object_ptr<RpWidget> MakeContent(
  31. not_null<Widget*> parent,
  32. Config &config) {
  33. if (config.content) {
  34. config.content->setParent(parent);
  35. config.content->show();
  36. return std::move(config.content);
  37. }
  38. auto lifetime = rpl::lifetime();
  39. const auto st = lifetime.make_state<style::FlatLabel>(
  40. st::defaultFlatLabel);
  41. st->style = config.st->style;
  42. st->textFg = st::toastFg;
  43. st->palette = config.st->palette;
  44. st->minWidth = config.padding
  45. ? style::ConvertScale(1) // We don't really know.
  46. : (config.st->minWidth
  47. - config.st->padding.left()
  48. - config.st->padding.right());
  49. st->maxHeight = config.st->style.font->height
  50. * (config.singleline ? 1 : config.maxlines);
  51. auto result = object_ptr<FlatLabel>(parent, QString(), *st);
  52. const auto raw = result.data();
  53. raw->lifetime().add(std::move(lifetime));
  54. auto context = config.textContext;
  55. context.repaint = [raw] { raw->update(); };
  56. raw->setMarkedText(ComputeText(config), context);
  57. raw->setClickHandlerFilter(std::move(config.filter));
  58. raw->show();
  59. return result;
  60. }
  61. [[nodiscard]] bool HasLinksOrSpoilers(const TextWithEntities &text) {
  62. for (const auto &entity : text.entities) {
  63. switch (entity.type()) {
  64. case EntityType::Url:
  65. case EntityType::CustomUrl:
  66. case EntityType::Email:
  67. case EntityType::Spoiler: return true;
  68. }
  69. }
  70. return false;
  71. }
  72. } // namespace
  73. Widget::Widget(QWidget *parent, Config &&config)
  74. : RpWidget(parent)
  75. , _st(config.st)
  76. , _roundRect(ImageRoundRadius::Large, st::toastBg)
  77. , _attach(config.attach)
  78. , _content(MakeContent(this, config))
  79. , _padding(config.padding
  80. ? std::move(config.padding) | rpl::map(rpl::mappers::_1 + _st->padding)
  81. : rpl::single(_st->padding) | rpl::type_erased())
  82. , _adaptive(config.adaptive) {
  83. if (HasLinksOrSpoilers(config.text) || config.acceptinput) {
  84. setMouseTracking(true);
  85. } else {
  86. setAttribute(Qt::WA_TransparentForMouseEvents);
  87. }
  88. _padding.value() | rpl::start_with_next([=] {
  89. parentResized();
  90. }, lifetime());
  91. crl::on_main_update_requests(
  92. ) | rpl::start_with_next([=] {//const auto filter = [=](not_null<QEvent*> e) {
  93. if (_attach == RectPart::None && _shownLevel < 1.) {
  94. scheduleChildrenPaintRestore();
  95. }
  96. }, lifetime());
  97. show();
  98. }
  99. void Widget::parentResized() {
  100. updateGeometry();
  101. }
  102. void Widget::updateGeometry() {
  103. auto width = _st->maxWidth;
  104. const auto padding = _padding.current();
  105. const auto added = padding.left() + padding.right();
  106. const auto natural = _content->naturalWidth();
  107. const auto max = (natural > 0) ? natural : _content->width();
  108. accumulate_min(width, max + added);
  109. accumulate_min(
  110. width,
  111. parentWidget()->width() - _st->margin.left() - _st->margin.right());
  112. if (_adaptive) {
  113. width = FindNiceTooltipWidth(0, width - added, [&](int width) {
  114. _content->resizeToWidth(width);
  115. return _content->heightNoMargins();
  116. }) + added;
  117. }
  118. _content->resizeToWidth(width - added);
  119. const auto minHeight = _st->icon.empty()
  120. ? 0
  121. : (_st->icon.height() + 2 * _st->iconPosition.y());
  122. const auto normalHeight = padding.top()
  123. + _content->heightNoMargins()
  124. + padding.bottom();
  125. const auto height = std::max(minHeight, normalHeight);
  126. const auto top = padding.top() + ((height - normalHeight) / 2);
  127. _content->moveToLeft(padding.left(), top);
  128. const auto rect = QRect(0, 0, width, height);
  129. const auto outer = parentWidget()->size();
  130. const auto full = QPoint(outer.width(), outer.height());
  131. const auto middle = QPoint(
  132. (outer.width() - width) / 2,
  133. (outer.height() - height) / 2);
  134. _updateShownGeometry = [=](float64 level) {
  135. const auto interpolated = [&](int from, int to) {
  136. return anim::interpolate(from, to, level);
  137. };
  138. setGeometry(rect.translated([&] {
  139. switch (_attach) {
  140. case RectPart::None:
  141. return middle;
  142. case RectPart::Left:
  143. return QPoint(
  144. interpolated(-width, _st->margin.left()),
  145. middle.y());
  146. case RectPart::Top:
  147. return QPoint(
  148. middle.x(),
  149. interpolated(-height, _st->margin.top()));
  150. case RectPart::Right:
  151. return QPoint(
  152. full.x() - interpolated(0, width + _st->margin.right()),
  153. middle.y());
  154. case RectPart::Bottom:
  155. return QPoint(
  156. middle.x(),
  157. full.y() - interpolated(0, height + _st->margin.bottom()));
  158. }
  159. Unexpected("Slide side in Toast::Widget::updateGeometry.");
  160. }()));
  161. };
  162. _updateShownGeometry(_shownLevel);
  163. }
  164. void Widget::setShownLevel(float64 shownLevel) {
  165. if (_shownLevel == shownLevel) {
  166. return;
  167. }
  168. _shownLevel = shownLevel;
  169. if (_attach != RectPart::None) {
  170. _updateShownGeometry(_shownLevel);
  171. } else {
  172. update();
  173. }
  174. }
  175. void Widget::paintToProxy() {
  176. const auto ratio = devicePixelRatio();
  177. const auto full = size() * ratio;
  178. if (_shownProxy.size() != full) {
  179. _shownProxy = QImage(full, QImage::Format_ARGB32_Premultiplied);
  180. }
  181. _shownProxy.setDevicePixelRatio(ratio);
  182. _shownProxy.fill(Qt::transparent);
  183. auto q = QPainter(&_shownProxy);
  184. const auto saved = std::exchange(_shownLevel, 1.);
  185. Ui::RenderWidget(q, this);
  186. _shownLevel = saved;
  187. }
  188. void Widget::disableChildrenPaintOnce() {
  189. toggleChildrenPaint(false);
  190. scheduleChildrenPaintRestore();
  191. }
  192. void Widget::toggleChildrenPaint(bool enabled) {
  193. _childrenPaintDisabled = !enabled;
  194. for (const auto child : children()) {
  195. if (child->isWidgetType()) {
  196. static_cast<QWidget*>(child)->setAttribute(
  197. Qt::WA_UpdatesDisabled,
  198. _childrenPaintDisabled);
  199. }
  200. }
  201. }
  202. void Widget::scheduleChildrenPaintRestore() {
  203. if (_childrenPaintRestoreScheduled) {
  204. return;
  205. }
  206. _childrenPaintRestoreScheduled = true;
  207. Ui::PostponeCall(this, [=] {
  208. _childrenPaintRestoreScheduled = false;
  209. if (_childrenPaintDisabled) {
  210. toggleChildrenPaint(true);
  211. }
  212. });
  213. }
  214. void Widget::paintEvent(QPaintEvent *e) {
  215. auto p = QPainter(this);
  216. const auto opacity = (_attach == RectPart::None)
  217. ? _shownLevel
  218. : 1.;
  219. if (opacity < 1.) {
  220. paintToProxy();
  221. p.setOpacity(opacity);
  222. p.drawImage(0, 0, _shownProxy);
  223. disableChildrenPaintOnce();
  224. return;
  225. }
  226. auto hq = PainterHighQualityEnabler(p);
  227. _roundRect.paint(p, rect());
  228. if (!_st->icon.empty()) {
  229. _st->icon.paint(
  230. p,
  231. _st->iconPosition.x(),
  232. _st->iconPosition.y(),
  233. width());
  234. }
  235. }
  236. } // namespace Ui::Toast::internal