Support complex dimensions in Ui::Text::String.

This commit is contained in:
John Preston 2023-11-02 20:19:28 +04:00
parent 607c3909dd
commit 24390ae853
9 changed files with 161 additions and 50 deletions

View file

@ -819,6 +819,10 @@ Object::~Object() {
unload();
}
int Object::width() {
return st::emojiSize + 2 * st::emojiPadding;
}
QString Object::entityData() {
return _instance->entityData();
}
@ -864,6 +868,10 @@ Internal::Internal(QString entityData, QImage image, bool colored)
, _colored(colored) {
}
int Internal::width() {
return _image.width() / _image.devicePixelRatio();
}
QString Internal::entityData() {
return _entityData;
}

View file

@ -258,6 +258,7 @@ public:
Object(not_null<Instance*> instance, Fn<void()> repaint);
~Object();
int width() override;
QString entityData() override;
void paint(QPainter &p, const Context &context) override;
void unload() override;
@ -273,10 +274,11 @@ private:
};
class Internal final : public Ui::Text::CustomEmoji {
class Internal final : public Text::CustomEmoji {
public:
Internal(QString entityData, QImage image, bool colored);
int width() override;
QString entityData() override;
void paint(QPainter &p, const Context &context) override;
void unload() override;

View file

@ -838,13 +838,37 @@ void String::removeModificationsAfter(int size) {
}
}
String::DimensionsResult String::countDimensions(
GeometryDescriptor geometry) const {
return countDimensions(std::move(geometry), {});
}
String::DimensionsResult String::countDimensions(
GeometryDescriptor geometry,
DimensionsRequest request) const {
auto result = DimensionsResult();
if (request.lineWidths && request.reserve) {
result.lineWidths.reserve(request.reserve);
}
enumerateLines(geometry, [&](QFixed lineWidth, int lineBottom) {
const auto width = lineWidth.ceil().toInt();
if (request.lineWidths) {
result.lineWidths.push_back(width);
}
result.width = std::max(result.width, width);
result.height = lineBottom;
});
return result;
}
int String::countWidth(int width, bool breakEverywhere) const {
if (QFixed(width) >= _maxWidth) {
return _maxWidth;
}
QFixed maxLineWidth = 0;
enumerateLines(width, breakEverywhere, [&](QFixed lineWidth, int lineHeight) {
enumerateLines(width, breakEverywhere, [&](QFixed lineWidth, int) {
if (lineWidth > maxLineWidth) {
maxLineWidth = lineWidth;
}
@ -857,8 +881,8 @@ int String::countHeight(int width, bool breakEverywhere) const {
return _minHeight;
}
int result = 0;
enumerateLines(width, breakEverywhere, [&](QFixed lineWidth, int lineHeight) {
result += lineHeight;
enumerateLines(width, breakEverywhere, [&](auto, int lineBottom) {
result = lineBottom;
});
return result;
}
@ -874,7 +898,7 @@ std::vector<int> String::countLineWidths(
if (options.reserve) {
result.reserve(options.reserve);
}
enumerateLines(width, options.breakEverywhere, [&](QFixed lineWidth, int lineHeight) {
enumerateLines(width, options.breakEverywhere, [&](QFixed lineWidth, int) {
result.push_back(lineWidth.ceil().toInt());
});
return result;
@ -884,18 +908,85 @@ template <typename Callback>
void String::enumerateLines(
int w,
bool breakEverywhere,
Callback callback) const {
const auto width = QFixed(std::max(w, _minResizeWidth));
Callback &&callback) const {
if (isEmpty()) {
return;
}
const auto width = std::max(w, _minResizeWidth);
auto g = SimpleGeometry(width, _st->font->height, 0, 0, false, false);
g.breakEverywhere = breakEverywhere;
enumerateLines(g, std::forward<Callback>(callback));
}
template <typename Callback>
void String::enumerateLines(
GeometryDescriptor geometry,
Callback &&callback) const {
auto qindex = 0;
auto quote = (QuoteDetails*)nullptr;
auto qpadding = QMargins();
auto top = 0;
auto lineLeft = 0;
auto lineWidth = 0;
auto widthLeft = QFixed(0);
auto paragraphWidthRemaining = QFixed();
const auto initNextLine = [&] {
const auto line = geometry.layout({
.left = 0,
.top = top,
.width = paragraphWidthRemaining.ceil().toInt(),
});
lineLeft = line.left;
lineWidth = line.width;
if (quote && quote->maxWidth < lineWidth) {
const auto delta = lineWidth - quote->maxWidth;
lineWidth = quote->maxWidth;
}
widthLeft = lineWidth - qpadding.left() - qpadding.right();
};
const auto initNextParagraph = [&](
TextBlocks::const_iterator i,
int16 paragraphIndex) {
paragraphWidthRemaining = 0;
if (qindex != paragraphIndex) {
top += qpadding.bottom();
qindex = paragraphIndex;
quote = quoteByIndex(qindex);
qpadding = quotePadding(quote);
top += qpadding.top();
qpadding.setTop(0);
}
const auto e = _blocks.cend();
if (i != e) {
auto last_rPadding = QFixed(0);
auto last_rBearing = QFixed(0);
for (; i != e; ++i) {
if ((*i)->type() == TextBlockType::Newline) {
break;
}
const auto rBearing = (*i)->f_rbearing();
paragraphWidthRemaining += last_rBearing
+ last_rPadding
+ (*i)->f_width()
- rBearing;
last_rBearing = rBearing;
}
}
paragraphWidthRemaining += qpadding.left() + qpadding.right();
initNextLine();
};
if ((*_blocks.cbegin())->type() != TextBlockType::Newline) {
initNextParagraph(_blocks.cbegin(), _startQuoteIndex);
}
auto qindex = quoteIndex(nullptr);
auto quote = quoteByIndex(qindex);
auto qpadding = quotePadding(quote);
auto widthLeft = width - qpadding.left() - qpadding.right();
auto lineHeight = 0;
auto last_rBearing = QFixed();
auto last_rPadding = QFixed();
bool longWordLine = true;
for (auto &b : _blocks) {
for (auto i = _blocks.cbegin(); i != _blocks.cend(); ++i) {
const auto &b = *i;
auto _btype = b->type();
const auto blockHeight = CountBlockHeight(b.get(), _st);
@ -903,25 +994,16 @@ void String::enumerateLines(
if (!lineHeight) {
lineHeight = blockHeight;
}
lineHeight += qpadding.top();
const auto index = quoteIndex(b.get());
if (qindex != index) {
const auto index = b.unsafe<const NewlineBlock>().quoteIndex();
const auto changed = (qindex != index);
if (changed) {
lineHeight += qpadding.bottom();
qindex = index;
quote = quoteByIndex(qindex);
qpadding = quotePadding(quote);
} else {
qpadding.setTop(0);
}
callback(width - widthLeft, lineHeight);
callback(lineLeft + lineWidth - widthLeft, top += lineHeight);
lineHeight = 0;
last_rBearing = 0;// b->f_rbearing(); (0 for newline)
last_rPadding = 0;// b->f_rpadding(); (0 for newline)
widthLeft = width - qpadding.left() - qpadding.right();
// - (b->f_width() - last_rBearing); (0 for newline)
initNextParagraph(i + 1, index);
longWordLine = true;
continue;
}
@ -974,23 +1056,23 @@ void String::enumerateLines(
continue;
}
if (f != j && !breakEverywhere) {
if (f != j && !geometry.breakEverywhere) {
j = f;
widthLeft = f_wLeft;
lineHeight = f_lineHeight;
j_width = (j->f_width() >= 0) ? j->f_width() : -j->f_width();
}
callback(width - widthLeft, lineHeight + qpadding.top());
qpadding.setTop(0);
callback(lineLeft + lineWidth - widthLeft, top += lineHeight);
lineHeight = qMax(0, blockHeight);
paragraphWidthRemaining -= (lineWidth - widthLeft) - last_rPadding + last_rBearing;
initNextLine();
last_rBearing = j->f_rbearing();
last_rPadding = j->f_rpadding();
widthLeft = width
- qpadding.left()
- qpadding.right()
- (j_width - last_rBearing);
widthLeft -= j_width - last_rBearing;
longWordLine = !wordEndsHere;
f = j + 1;
@ -1000,24 +1082,23 @@ void String::enumerateLines(
continue;
}
callback(width - widthLeft, lineHeight + qpadding.top());
qpadding.setTop(0);
callback(lineLeft + lineWidth - widthLeft, top += lineHeight);
lineHeight = qMax(0, blockHeight);
paragraphWidthRemaining -= (lineWidth - widthLeft) - last_rPadding + last_rBearing;
initNextLine();
last_rBearing = b__f_rbearing;
last_rPadding = b->f_rpadding();
widthLeft = width
- qpadding.left()
- qpadding.right()
- (b->f_width() - last_rBearing);
widthLeft -= b->f_width() - last_rBearing;
longWordLine = true;
continue;
}
if (widthLeft < width) {
if (widthLeft < lineWidth) {
callback(
width - widthLeft,
lineHeight + qpadding.top() + qpadding.bottom());
lineLeft + lineWidth - widthLeft,
top + lineHeight + qpadding.bottom());
}
}

View file

@ -275,6 +275,7 @@ public:
std::vector<int> lineWidths;
};
struct DimensionsRequest {
bool breakEverywhere = false;
bool lineWidths = false;
int reserve = 0;
};
@ -414,13 +415,17 @@ private:
FlagsChangeCallback flagsChangeCallback) const;
// Template method for countWidth(), countHeight(), countLineWidths().
// callback(lineWidth, lineHeight) will be called for all lines with:
// QFixed lineWidth, int lineHeight
// callback(lineWidth, lineBottom) will be called for all lines with:
// QFixed lineWidth, int lineBottom
template <typename Callback>
void enumerateLines(
int w,
bool breakEverywhere,
Callback callback) const;
Callback &&callback) const;
template <typename Callback>
void enumerateLines(
GeometryDescriptor geometry,
Callback &&callback) const;
void insertModifications(int position, int delta);
void removeModificationsAfter(int size);

View file

@ -551,7 +551,7 @@ CustomEmojiBlock::CustomEmojiBlock(
linkIndex,
colorIndex)
, _custom(std::move(custom)) {
_width = int(st::emojiSize + 2 * st::emojiPadding);
_width = _custom->width();
_rpadding = 0;
for (auto i = length; i != 0;) {
auto ch = text[_position + (--i)];

View file

@ -19,6 +19,10 @@ ShiftedEmoji::ShiftedEmoji(
, _shift(shift) {
}
int ShiftedEmoji::width() {
return _wrapped->width();
}
QString ShiftedEmoji::entityData() {
return _wrapped->entityData();
}
@ -45,6 +49,10 @@ FirstFrameEmoji::FirstFrameEmoji(std::unique_ptr<CustomEmoji> wrapped)
: _wrapped(std::move(wrapped)) {
}
int FirstFrameEmoji::width() {
return _wrapped->width();
}
QString FirstFrameEmoji::entityData() {
return _wrapped->entityData();
}
@ -77,6 +85,10 @@ LimitedLoopsEmoji::LimitedLoopsEmoji(
, _stopOnLast(stopOnLast) {
}
int LimitedLoopsEmoji::width() {
return _wrapped->width();
}
QString LimitedLoopsEmoji::entityData() {
return _wrapped->entityData();
}

View file

@ -40,6 +40,7 @@ class CustomEmoji {
public:
virtual ~CustomEmoji() = default;
[[nodiscard]] virtual int width() = 0;
[[nodiscard]] virtual QString entityData() = 0;
using Context = CustomEmojiPaintContext;
@ -58,6 +59,7 @@ class ShiftedEmoji final : public CustomEmoji {
public:
ShiftedEmoji(std::unique_ptr<CustomEmoji> wrapped, QPoint shift);
int width() override;
QString entityData() override;
void paint(QPainter &p, const Context &context) override;
void unload() override;
@ -74,6 +76,7 @@ class FirstFrameEmoji final : public CustomEmoji {
public:
explicit FirstFrameEmoji(std::unique_ptr<CustomEmoji> wrapped);
int width() override;
QString entityData() override;
void paint(QPainter &p, const Context &context) override;
void unload() override;
@ -92,6 +95,7 @@ public:
int limit,
bool stopOnLast = false);
int width() override;
QString entityData() override;
void paint(QPainter &p, const Context &context) override;
void unload() override;

View file

@ -949,14 +949,14 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
const auto color = (selected
? _currentPenSelected
: _currentPen)->color();
if (!_customEmojiSize) {
_customEmojiSize = AdjustCustomEmojiSize(st::emojiSize);
_customEmojiSkip = (st::emojiSize - _customEmojiSize) / 2;
if (!_customEmojiContext) {
_customEmojiContext = CustomEmoji::Context{
.textColor = color,
.now = now(),
.paused = _pausedEmoji,
};
_customEmojiSkip = (st::emojiSize
- AdjustCustomEmojiSize(st::emojiSize)) / 2;
} else {
_customEmojiContext->textColor = color;
}

View file

@ -169,7 +169,6 @@ private:
QVarLengthArray<QRect, kSpoilersRectsSize> _highlightRects;
std::optional<CustomEmoji::Context> _customEmojiContext;
int _customEmojiSize = 0;
int _customEmojiSkip = 0;
int _indexOfElidedBlock = -1; // For spoilers.