Files
tdesktop/Telegram/lib_ui/ui/widgets/fields/custom_field_object.cpp
allhaileris afb81b8278
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
init
2026-02-16 15:50:16 +03:00

305 lines
8.3 KiB
C++

// 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 &quote = _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