| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- // This file is part of Desktop App Toolkit,
- // a set of libraries for developing nice desktop applications.
- //
- // For license and copyright information please follow this link:
- // https://github.com/desktop-app/legal/blob/master/LEGAL
- //
- #include "ui/widgets/fields/custom_field_object.h"
- #include "ui/effects/spoiler_mess.h"
- #include "ui/text/text.h"
- #include "ui/text/text_renderer.h"
- #include "ui/text/text_utilities.h"
- #include "ui/widgets/fields/input_field.h"
- #include "ui/integration.h"
- #include "styles/style_basic.h"
- #include "styles/style_widgets.h"
- #include <QtWidgets/QTextEdit>
- #include <QtWidgets/QScrollBar>
- namespace Ui {
- namespace {
- constexpr auto kSpoilerHiddenOpacity = 0.5;
- using SpoilerRect = InputFieldSpoilerRect;
- } // namespace
- class FieldSpoilerOverlay final : public RpWidget {
- public:
- FieldSpoilerOverlay(
- not_null<InputField*> field,
- Fn<float64()> shown,
- Fn<bool()> paused);
- private:
- void paintEvent(QPaintEvent *e) override;
- const not_null<InputField*> _field;
- const Fn<float64()> _shown;
- const Fn<bool()> _paused;
- SpoilerAnimation _animation;
- };
- FieldSpoilerOverlay::FieldSpoilerOverlay(
- not_null<InputField*> field,
- Fn<float64()> shown,
- Fn<bool()> paused)
- : RpWidget(field->rawTextEdit())
- , _field(field)
- , _shown(std::move(shown))
- , _paused(std::move(paused))
- , _animation([=] { update(); }) {
- setAttribute(Qt::WA_TransparentForMouseEvents);
- show();
- }
- void FieldSpoilerOverlay::paintEvent(QPaintEvent *e) {
- auto p = std::optional<QPainter>();
- auto topShift = std::optional<int>();
- auto frame = std::optional<SpoilerMessFrame>();
- auto blockquoteBg = std::optional<QColor>();
- const auto clip = e->rect();
- const auto shown = _shown();
- const auto bgOpacity = shown;
- const auto fgOpacity = 1. * shown + kSpoilerHiddenOpacity * (1. - shown);
- for (const auto &rect : _field->_spoilerRects) {
- const auto fill = rect.geometry.intersected(clip);
- if (fill.isEmpty()) {
- continue;
- } else if (!p) {
- p.emplace(this);
- const auto paused = _paused && _paused();
- frame.emplace(
- Text::DefaultSpoilerCache()->lookup(
- st::defaultTextPalette.spoilerFg->c)->frame(
- _animation.index(crl::now(), paused)));
- topShift = -_field->rawTextEdit()->verticalScrollBar()->value();
- }
- if (bgOpacity > 0.) {
- p->setOpacity(bgOpacity);
- if (rect.blockquote && !blockquoteBg) {
- const auto bg = _field->_blockquoteBg;
- blockquoteBg = (bg.alphaF() < 1.)
- ? anim::color(
- _field->_st.textBg->c,
- QColor(bg.red(), bg.green(), bg.blue()),
- bg.alphaF())
- : bg;
- }
- p->fillRect(
- fill,
- rect.blockquote ? *blockquoteBg : _field->_st.textBg->c);
- }
- p->setOpacity(fgOpacity);
- const auto shift = QPoint(0, *topShift) - rect.geometry.topLeft();
- FillSpoilerRect(*p, rect.geometry, *frame, shift);
- }
- }
- CustomFieldObject::CustomFieldObject(
- not_null<InputField*> field,
- Text::MarkedContext context,
- Fn<bool()> pausedEmoji,
- Fn<bool()> pausedSpoiler)
- : _field(field)
- , _context(std::move(context))
- , _pausedEmoji(std::move(pausedEmoji))
- , _pausedSpoiler(std::move(pausedSpoiler))
- , _factory(makeFactory())
- , _now(crl::now()) {
- }
- CustomFieldObject::~CustomFieldObject() = default;
- void *CustomFieldObject::qt_metacast(const char *iid) {
- if (QLatin1String(iid) == qobject_interface_iid<QTextObjectInterface*>()) {
- return static_cast<QTextObjectInterface*>(this);
- }
- return QObject::qt_metacast(iid);
- }
- QSizeF CustomFieldObject::intrinsicSize(
- QTextDocument *doc,
- int posInDocument,
- const QTextFormat &format) {
- const auto line = _field->_st.style.font->height;
- if (format.objectType() == InputField::kCollapsedQuoteFormat) {
- const auto &padding = _field->_st.style.blockquote.padding;
- const auto paddings = padding.left() + padding.right();
- const auto skip = 2 * doc->documentMargin();
- const auto height = Text::kQuoteCollapsedLines * line;
- return QSizeF(doc->pageSize().width() - paddings - skip, height);
- }
- const auto size = st::emojiSize * 1.;
- const auto width = size + st::emojiPadding * 2.;
- const auto height = std::max(line * 1., size);
- if (!_skip) {
- const auto emoji = Text::AdjustCustomEmojiSize(st::emojiSize);
- _skip = (st::emojiSize - emoji) / 2;
- }
- return { width, height };
- }
- void CustomFieldObject::drawObject(
- QPainter *painter,
- const QRectF &rect,
- QTextDocument *doc,
- int posInDocument,
- const QTextFormat &format) {
- if (format.objectType() == InputField::kCollapsedQuoteFormat) {
- const auto left = 0;
- const auto top = 0;
- const auto id = format.property(InputField::kQuoteId).toInt();
- if (const auto i = _quotes.find(id); i != end(_quotes)) {
- i->second.string.draw(*painter, {
- .position = QPoint(left + rect.x(), top + rect.y()),
- .outerWidth = int(base::SafeRound(doc->pageSize().width())),
- .availableWidth = int(std::floor(rect.width())),
- .palette = nullptr,
- .spoiler = Text::DefaultSpoilerCache(),
- .now = _now,
- .pausedEmoji = _pausedEmoji(),
- .pausedSpoiler = _pausedSpoiler(),
- .elisionLines = Text::kQuoteCollapsedLines,
- .useFullWidth = true,
- });
- }
- return;
- }
- const auto id = format.property(InputField::kCustomEmojiId).toULongLong();
- if (!id) {
- return;
- }
- auto i = _emoji.find(id);
- if (i == end(_emoji)) {
- const auto link = format.property(InputField::kCustomEmojiLink);
- const auto data = InputField::CustomEmojiEntityData(link.toString());
- if (auto emoji = _factory(data)) {
- i = _emoji.emplace(id, std::move(emoji)).first;
- }
- }
- if (i == end(_emoji)) {
- return;
- }
- i->second->paint(*painter, {
- .textColor = format.foreground().color(),
- .now = _now,
- .position = QPoint(
- int(base::SafeRound(rect.x())) + st::emojiPadding + _skip,
- int(base::SafeRound(rect.y())) + _skip),
- .paused = _pausedEmoji && _pausedEmoji(),
- });
- }
- void CustomFieldObject::clearEmoji() {
- _emoji.clear();
- }
- void CustomFieldObject::clearQuotes() {
- _quotes.clear();
- }
- std::unique_ptr<RpWidget> CustomFieldObject::createSpoilerOverlay() {
- return std::make_unique<FieldSpoilerOverlay>(
- _field,
- [=] { return _spoilerOpacity.value(_spoilerHidden ? 0. : 1.); },
- _pausedSpoiler);
- }
- void CustomFieldObject::refreshSpoilerShown(InputFieldTextRange range) {
- auto hidden = false;
- using Range = InputFieldTextRange;
- const auto intersects = [](Range a, Range b) {
- return (a.from < b.till) && (b.from < a.till);
- };
- if (range.till > range.from) {
- const auto check = [&](const std::vector<Range> &list) {
- if (!hidden) {
- for (const auto &spoiler : list) {
- if (intersects(spoiler, range)) {
- hidden = true;
- break;
- }
- }
- }
- };
- check(_field->_spoilerRangesText);
- check(_field->_spoilerRangesEmoji);
- } else {
- auto touchesLeft = false;
- auto touchesRight = false;
- const auto cursor = range.from;
- const auto check = [&](const std::vector<Range> &list) {
- if (!touchesLeft || !touchesRight) {
- for (const auto &spoiler : list) {
- if (spoiler.from <= cursor && spoiler.till >= cursor) {
- if (spoiler.from < cursor) {
- touchesLeft = true;
- }
- if (spoiler.till >= cursor) {
- touchesRight = true;
- }
- if (touchesLeft && touchesRight) {
- break;
- }
- }
- }
- }
- };
- check(_field->_spoilerRangesText);
- check(_field->_spoilerRangesEmoji);
- hidden = touchesLeft && touchesRight;
- }
- if (_spoilerHidden != hidden) {
- _spoilerHidden = hidden;
- _spoilerOpacity.start(
- [=] { _field->update(); },
- hidden ? 1. : 0.,
- hidden ? 0. : 1.,
- st::fadeWrapDuration);
- }
- }
- void CustomFieldObject::setCollapsedText(int quoteId, TextWithTags text) {
- auto "e = _quotes[quoteId];
- quote.string = Text::String(_field->_st.widthMin);
- quote.string.setMarkedText(_field->_st.style, {
- text.text,
- TextUtilities::ConvertTextTagsToEntities(text.tags),
- }, kMarkupTextOptions, makeFieldContext());
- quote.text = std::move(text);
- }
- const TextWithTags &CustomFieldObject::collapsedText(int quoteId) const {
- if (const auto i = _quotes.find(quoteId); i != end(_quotes)) {
- return i->second.text;
- }
- static const auto kEmpty = TextWithTags();
- return kEmpty;
- }
- Text::MarkedContext CustomFieldObject::makeFieldContext() {
- auto context = _context;
- context.repaint = [field = _field] { field->update(); };
- return context;
- }
- CustomFieldObject::Factory CustomFieldObject::makeFactory() {
- return [context = makeFieldContext()](QStringView data) {
- return Text::MakeCustomEmoji(data, context);
- };
- }
- void CustomFieldObject::setNow(crl::time now) {
- _now = now;
- }
- } // namespace Ui
|