custom_field_object.cpp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  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/widgets/fields/custom_field_object.h"
  8. #include "ui/effects/spoiler_mess.h"
  9. #include "ui/text/text.h"
  10. #include "ui/text/text_renderer.h"
  11. #include "ui/text/text_utilities.h"
  12. #include "ui/widgets/fields/input_field.h"
  13. #include "ui/integration.h"
  14. #include "styles/style_basic.h"
  15. #include "styles/style_widgets.h"
  16. #include <QtWidgets/QTextEdit>
  17. #include <QtWidgets/QScrollBar>
  18. namespace Ui {
  19. namespace {
  20. constexpr auto kSpoilerHiddenOpacity = 0.5;
  21. using SpoilerRect = InputFieldSpoilerRect;
  22. } // namespace
  23. class FieldSpoilerOverlay final : public RpWidget {
  24. public:
  25. FieldSpoilerOverlay(
  26. not_null<InputField*> field,
  27. Fn<float64()> shown,
  28. Fn<bool()> paused);
  29. private:
  30. void paintEvent(QPaintEvent *e) override;
  31. const not_null<InputField*> _field;
  32. const Fn<float64()> _shown;
  33. const Fn<bool()> _paused;
  34. SpoilerAnimation _animation;
  35. };
  36. FieldSpoilerOverlay::FieldSpoilerOverlay(
  37. not_null<InputField*> field,
  38. Fn<float64()> shown,
  39. Fn<bool()> paused)
  40. : RpWidget(field->rawTextEdit())
  41. , _field(field)
  42. , _shown(std::move(shown))
  43. , _paused(std::move(paused))
  44. , _animation([=] { update(); }) {
  45. setAttribute(Qt::WA_TransparentForMouseEvents);
  46. show();
  47. }
  48. void FieldSpoilerOverlay::paintEvent(QPaintEvent *e) {
  49. auto p = std::optional<QPainter>();
  50. auto topShift = std::optional<int>();
  51. auto frame = std::optional<SpoilerMessFrame>();
  52. auto blockquoteBg = std::optional<QColor>();
  53. const auto clip = e->rect();
  54. const auto shown = _shown();
  55. const auto bgOpacity = shown;
  56. const auto fgOpacity = 1. * shown + kSpoilerHiddenOpacity * (1. - shown);
  57. for (const auto &rect : _field->_spoilerRects) {
  58. const auto fill = rect.geometry.intersected(clip);
  59. if (fill.isEmpty()) {
  60. continue;
  61. } else if (!p) {
  62. p.emplace(this);
  63. const auto paused = _paused && _paused();
  64. frame.emplace(
  65. Text::DefaultSpoilerCache()->lookup(
  66. st::defaultTextPalette.spoilerFg->c)->frame(
  67. _animation.index(crl::now(), paused)));
  68. topShift = -_field->rawTextEdit()->verticalScrollBar()->value();
  69. }
  70. if (bgOpacity > 0.) {
  71. p->setOpacity(bgOpacity);
  72. if (rect.blockquote && !blockquoteBg) {
  73. const auto bg = _field->_blockquoteBg;
  74. blockquoteBg = (bg.alphaF() < 1.)
  75. ? anim::color(
  76. _field->_st.textBg->c,
  77. QColor(bg.red(), bg.green(), bg.blue()),
  78. bg.alphaF())
  79. : bg;
  80. }
  81. p->fillRect(
  82. fill,
  83. rect.blockquote ? *blockquoteBg : _field->_st.textBg->c);
  84. }
  85. p->setOpacity(fgOpacity);
  86. const auto shift = QPoint(0, *topShift) - rect.geometry.topLeft();
  87. FillSpoilerRect(*p, rect.geometry, *frame, shift);
  88. }
  89. }
  90. CustomFieldObject::CustomFieldObject(
  91. not_null<InputField*> field,
  92. Text::MarkedContext context,
  93. Fn<bool()> pausedEmoji,
  94. Fn<bool()> pausedSpoiler)
  95. : _field(field)
  96. , _context(std::move(context))
  97. , _pausedEmoji(std::move(pausedEmoji))
  98. , _pausedSpoiler(std::move(pausedSpoiler))
  99. , _factory(makeFactory())
  100. , _now(crl::now()) {
  101. }
  102. CustomFieldObject::~CustomFieldObject() = default;
  103. void *CustomFieldObject::qt_metacast(const char *iid) {
  104. if (QLatin1String(iid) == qobject_interface_iid<QTextObjectInterface*>()) {
  105. return static_cast<QTextObjectInterface*>(this);
  106. }
  107. return QObject::qt_metacast(iid);
  108. }
  109. QSizeF CustomFieldObject::intrinsicSize(
  110. QTextDocument *doc,
  111. int posInDocument,
  112. const QTextFormat &format) {
  113. const auto line = _field->_st.style.font->height;
  114. if (format.objectType() == InputField::kCollapsedQuoteFormat) {
  115. const auto &padding = _field->_st.style.blockquote.padding;
  116. const auto paddings = padding.left() + padding.right();
  117. const auto skip = 2 * doc->documentMargin();
  118. const auto height = Text::kQuoteCollapsedLines * line;
  119. return QSizeF(doc->pageSize().width() - paddings - skip, height);
  120. }
  121. const auto size = st::emojiSize * 1.;
  122. const auto width = size + st::emojiPadding * 2.;
  123. const auto height = std::max(line * 1., size);
  124. if (!_skip) {
  125. const auto emoji = Text::AdjustCustomEmojiSize(st::emojiSize);
  126. _skip = (st::emojiSize - emoji) / 2;
  127. }
  128. return { width, height };
  129. }
  130. void CustomFieldObject::drawObject(
  131. QPainter *painter,
  132. const QRectF &rect,
  133. QTextDocument *doc,
  134. int posInDocument,
  135. const QTextFormat &format) {
  136. if (format.objectType() == InputField::kCollapsedQuoteFormat) {
  137. const auto left = 0;
  138. const auto top = 0;
  139. const auto id = format.property(InputField::kQuoteId).toInt();
  140. if (const auto i = _quotes.find(id); i != end(_quotes)) {
  141. i->second.string.draw(*painter, {
  142. .position = QPoint(left + rect.x(), top + rect.y()),
  143. .outerWidth = int(base::SafeRound(doc->pageSize().width())),
  144. .availableWidth = int(std::floor(rect.width())),
  145. .palette = nullptr,
  146. .spoiler = Text::DefaultSpoilerCache(),
  147. .now = _now,
  148. .pausedEmoji = _pausedEmoji(),
  149. .pausedSpoiler = _pausedSpoiler(),
  150. .elisionLines = Text::kQuoteCollapsedLines,
  151. .useFullWidth = true,
  152. });
  153. }
  154. return;
  155. }
  156. const auto id = format.property(InputField::kCustomEmojiId).toULongLong();
  157. if (!id) {
  158. return;
  159. }
  160. auto i = _emoji.find(id);
  161. if (i == end(_emoji)) {
  162. const auto link = format.property(InputField::kCustomEmojiLink);
  163. const auto data = InputField::CustomEmojiEntityData(link.toString());
  164. if (auto emoji = _factory(data)) {
  165. i = _emoji.emplace(id, std::move(emoji)).first;
  166. }
  167. }
  168. if (i == end(_emoji)) {
  169. return;
  170. }
  171. i->second->paint(*painter, {
  172. .textColor = format.foreground().color(),
  173. .now = _now,
  174. .position = QPoint(
  175. int(base::SafeRound(rect.x())) + st::emojiPadding + _skip,
  176. int(base::SafeRound(rect.y())) + _skip),
  177. .paused = _pausedEmoji && _pausedEmoji(),
  178. });
  179. }
  180. void CustomFieldObject::clearEmoji() {
  181. _emoji.clear();
  182. }
  183. void CustomFieldObject::clearQuotes() {
  184. _quotes.clear();
  185. }
  186. std::unique_ptr<RpWidget> CustomFieldObject::createSpoilerOverlay() {
  187. return std::make_unique<FieldSpoilerOverlay>(
  188. _field,
  189. [=] { return _spoilerOpacity.value(_spoilerHidden ? 0. : 1.); },
  190. _pausedSpoiler);
  191. }
  192. void CustomFieldObject::refreshSpoilerShown(InputFieldTextRange range) {
  193. auto hidden = false;
  194. using Range = InputFieldTextRange;
  195. const auto intersects = [](Range a, Range b) {
  196. return (a.from < b.till) && (b.from < a.till);
  197. };
  198. if (range.till > range.from) {
  199. const auto check = [&](const std::vector<Range> &list) {
  200. if (!hidden) {
  201. for (const auto &spoiler : list) {
  202. if (intersects(spoiler, range)) {
  203. hidden = true;
  204. break;
  205. }
  206. }
  207. }
  208. };
  209. check(_field->_spoilerRangesText);
  210. check(_field->_spoilerRangesEmoji);
  211. } else {
  212. auto touchesLeft = false;
  213. auto touchesRight = false;
  214. const auto cursor = range.from;
  215. const auto check = [&](const std::vector<Range> &list) {
  216. if (!touchesLeft || !touchesRight) {
  217. for (const auto &spoiler : list) {
  218. if (spoiler.from <= cursor && spoiler.till >= cursor) {
  219. if (spoiler.from < cursor) {
  220. touchesLeft = true;
  221. }
  222. if (spoiler.till >= cursor) {
  223. touchesRight = true;
  224. }
  225. if (touchesLeft && touchesRight) {
  226. break;
  227. }
  228. }
  229. }
  230. }
  231. };
  232. check(_field->_spoilerRangesText);
  233. check(_field->_spoilerRangesEmoji);
  234. hidden = touchesLeft && touchesRight;
  235. }
  236. if (_spoilerHidden != hidden) {
  237. _spoilerHidden = hidden;
  238. _spoilerOpacity.start(
  239. [=] { _field->update(); },
  240. hidden ? 1. : 0.,
  241. hidden ? 0. : 1.,
  242. st::fadeWrapDuration);
  243. }
  244. }
  245. void CustomFieldObject::setCollapsedText(int quoteId, TextWithTags text) {
  246. auto &quote = _quotes[quoteId];
  247. quote.string = Text::String(_field->_st.widthMin);
  248. quote.string.setMarkedText(_field->_st.style, {
  249. text.text,
  250. TextUtilities::ConvertTextTagsToEntities(text.tags),
  251. }, kMarkupTextOptions, makeFieldContext());
  252. quote.text = std::move(text);
  253. }
  254. const TextWithTags &CustomFieldObject::collapsedText(int quoteId) const {
  255. if (const auto i = _quotes.find(quoteId); i != end(_quotes)) {
  256. return i->second.text;
  257. }
  258. static const auto kEmpty = TextWithTags();
  259. return kEmpty;
  260. }
  261. Text::MarkedContext CustomFieldObject::makeFieldContext() {
  262. auto context = _context;
  263. context.repaint = [field = _field] { field->update(); };
  264. return context;
  265. }
  266. CustomFieldObject::Factory CustomFieldObject::makeFactory() {
  267. return [context = makeFieldContext()](QStringView data) {
  268. return Text::MakeCustomEmoji(data, context);
  269. };
  270. }
  271. void CustomFieldObject::setNow(crl::time now) {
  272. _now = now;
  273. }
  274. } // namespace Ui