Paint nice code blocks.
This commit is contained in:
parent
ab5057f001
commit
a38b60636a
9 changed files with 431 additions and 53 deletions
|
|
@ -23,7 +23,11 @@ TextStyle {
|
|||
font: font;
|
||||
linkUnderline: int;
|
||||
blockPadding: margins;
|
||||
blockVerticalSkip: pixels;
|
||||
blockHeader: pixels;
|
||||
blockHeaderPosition: point;
|
||||
blockOutline: pixels;
|
||||
blockRadius: pixels;
|
||||
preScrollable: bool;
|
||||
lineHeight: pixels;
|
||||
}
|
||||
|
|
@ -57,9 +61,6 @@ defaultTextPalette: TextPalette {
|
|||
defaultTextStyle: TextStyle {
|
||||
font: normalFont;
|
||||
linkUnderline: kLinkUnderlineActive;
|
||||
blockPadding: margins(10px, 4px, 6px, 4px);
|
||||
blockOutline: 3px;
|
||||
preScrollable: true;
|
||||
lineHeight: 0px;
|
||||
}
|
||||
semiboldTextStyle: TextStyle(defaultTextStyle) {
|
||||
|
|
|
|||
293
ui/text/text.cpp
293
ui/text/text.cpp
|
|
@ -185,6 +185,144 @@ GeometryDescriptor SimpleGeometry(
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
void ValidateBlockPaintCache(
|
||||
BlockPaintCache &cache,
|
||||
const style::TextStyle &st) {
|
||||
if (!cache.corners.isNull()
|
||||
&& cache.bgCached == cache.bg
|
||||
&& cache.outlineCached == cache.outline
|
||||
&& (!cache.withHeader || cache.headerCached == cache.header)
|
||||
&& (!cache.topright || cache.iconCached == cache.icon)) {
|
||||
return;
|
||||
}
|
||||
cache.bgCached = cache.bg;
|
||||
cache.outlineCached = cache.outline;
|
||||
if (cache.withHeader) {
|
||||
cache.headerCached = cache.header;
|
||||
}
|
||||
if (cache.topright) {
|
||||
cache.iconCached = cache.icon;
|
||||
}
|
||||
const auto radius = st.blockRadius;
|
||||
const auto header = cache.withHeader ? st.blockHeader : 0;
|
||||
const auto outline = st.blockOutline;
|
||||
const auto corner = std::max({ header, radius, outline });
|
||||
const auto middle = st::lineWidth;
|
||||
const auto side = 2 * corner + middle;
|
||||
const auto full = QSize(side, side);
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
auto image = QImage(full * ratio, QImage::Format_ARGB32_Premultiplied);
|
||||
image.fill(Qt::transparent);
|
||||
image.setDevicePixelRatio(ratio);
|
||||
auto p = QPainter(&image);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
|
||||
auto rect = QRect(QPoint(), full);
|
||||
if (header) {
|
||||
p.setBrush(cache.header);
|
||||
p.setClipRect(outline, 0, side - outline, header);
|
||||
p.drawRoundedRect(0, 0, side, corner + radius, radius, radius);
|
||||
}
|
||||
if (outline) {
|
||||
p.setBrush(cache.outline);
|
||||
p.setClipRect(0, 0, outline, side);
|
||||
p.drawRoundedRect(0, 0, outline + radius * 2, side, radius, radius);
|
||||
}
|
||||
p.setBrush(cache.bg);
|
||||
p.setClipRect(outline, header, side - outline, side - header);
|
||||
p.drawRoundedRect(0, 0, side, side, radius, radius);
|
||||
|
||||
p.end();
|
||||
cache.corners = std::move(image);
|
||||
}
|
||||
|
||||
void FillBlockPaint(
|
||||
QPainter &p,
|
||||
QRect rect,
|
||||
const BlockPaintCache &cache,
|
||||
const style::TextStyle &st,
|
||||
SkipBlockPaintParts parts) {
|
||||
const auto &image = cache.corners;
|
||||
const auto ratio = int(image.devicePixelRatio());
|
||||
const auto iwidth = image.width() / ratio;
|
||||
const auto iheight = image.height() / ratio;
|
||||
const auto imiddle = st::lineWidth;
|
||||
const auto ihalf = (iheight - imiddle) / 2;
|
||||
const auto x = rect.left();
|
||||
const auto width = rect.width();
|
||||
auto y = rect.top();
|
||||
auto height = rect.height();
|
||||
if (!parts.skipTop) {
|
||||
const auto top = std::min(height, ihalf);
|
||||
p.drawImage(
|
||||
QRect(x, y, ihalf, top),
|
||||
image,
|
||||
QRect(0, 0, ihalf * ratio, top * ratio));
|
||||
p.drawImage(
|
||||
QRect(x + width - ihalf, y, ihalf, top),
|
||||
image,
|
||||
QRect((iwidth - ihalf) * ratio, 0, ihalf * ratio, top * ratio));
|
||||
if (const auto middle = width - 2 * ihalf) {
|
||||
const auto header = cache.withHeader ? st.blockHeader : 0;
|
||||
const auto fillHeader = std::min(header, top);
|
||||
if (fillHeader) {
|
||||
p.fillRect(x + ihalf, y, middle, fillHeader, cache.header);
|
||||
}
|
||||
if (const auto fillBody = top - fillHeader) {
|
||||
p.fillRect(
|
||||
QRect(x + ihalf, y + fillHeader, middle, fillBody),
|
||||
cache.bg);
|
||||
}
|
||||
}
|
||||
height -= top;
|
||||
if (!height) {
|
||||
return;
|
||||
}
|
||||
y += top;
|
||||
rect.setTop(y);
|
||||
}
|
||||
if (!parts.skipBottom) {
|
||||
const auto bottom = std::min(height, ihalf);
|
||||
p.drawImage(
|
||||
QRect(x, y + height - bottom, ihalf, bottom),
|
||||
image,
|
||||
QRect(
|
||||
0,
|
||||
(iheight - bottom) * ratio,
|
||||
ihalf * ratio,
|
||||
bottom * ratio));
|
||||
p.drawImage(
|
||||
QRect(
|
||||
x + width - ihalf,
|
||||
y + height - bottom,
|
||||
ihalf,
|
||||
bottom),
|
||||
image,
|
||||
QRect(
|
||||
(iwidth - ihalf) * ratio,
|
||||
(iheight - bottom) * ratio,
|
||||
ihalf * ratio,
|
||||
bottom * ratio));
|
||||
if (const auto middle = width - 2 * ihalf) {
|
||||
p.fillRect(
|
||||
QRect(x + ihalf, y + height - bottom, middle, bottom),
|
||||
cache.bg);
|
||||
}
|
||||
height -= bottom;
|
||||
if (!height) {
|
||||
return;
|
||||
}
|
||||
rect.setHeight(height);
|
||||
}
|
||||
const auto outline = st.blockOutline;
|
||||
if (outline) {
|
||||
p.fillRect(x, y, outline, height, cache.outline);
|
||||
}
|
||||
p.fillRect(x + outline, y, width - outline, height, cache.bg);
|
||||
}
|
||||
|
||||
String::ExtendedWrap::ExtendedWrap() noexcept = default;
|
||||
|
||||
String::ExtendedWrap::ExtendedWrap(ExtendedWrap &&other) noexcept
|
||||
|
|
@ -283,18 +421,34 @@ void String::recountNaturalSize(
|
|||
}
|
||||
};
|
||||
|
||||
_maxWidth = _minHeight = 0;
|
||||
int32 lineHeight = 0;
|
||||
QFixed maxWidth = 0;
|
||||
QFixed width = 0, last_rBearing = 0, last_rPadding = 0;
|
||||
auto pindex = paragraphIndex(nullptr);
|
||||
auto paragraph = paragraphByIndex(pindex);
|
||||
auto ppadding = paragraphPadding(paragraph);
|
||||
|
||||
_maxWidth = 0;
|
||||
_minHeight = ppadding.top();
|
||||
auto lineHeight = 0;
|
||||
auto maxWidth = QFixed();
|
||||
auto width = QFixed(ppadding.left() + ppadding.right());
|
||||
auto last_rBearing = QFixed();
|
||||
auto last_rPadding = QFixed();
|
||||
for (auto &block : _blocks) {
|
||||
auto b = block.get();
|
||||
auto _btype = b->type();
|
||||
auto blockHeight = CountBlockHeight(b, _st);
|
||||
const auto b = block.get();
|
||||
const auto _btype = b->type();
|
||||
const auto blockHeight = CountBlockHeight(b, _st);
|
||||
if (_btype == TextBlockType::Newline) {
|
||||
if (!lineHeight) {
|
||||
lineHeight = blockHeight;
|
||||
}
|
||||
const auto index = paragraphIndex(b);
|
||||
if (pindex != index) {
|
||||
_minHeight += ppadding.bottom();
|
||||
pindex = index;
|
||||
paragraph = paragraphByIndex(pindex);
|
||||
ppadding = paragraphPadding(paragraph);
|
||||
_minHeight += ppadding.top();
|
||||
ppadding.setTop(0);
|
||||
}
|
||||
if (initial) {
|
||||
computeParagraphDirection(b->position());
|
||||
}
|
||||
|
|
@ -303,11 +457,12 @@ void String::recountNaturalSize(
|
|||
|
||||
_minHeight += lineHeight;
|
||||
lineHeight = 0;
|
||||
last_rBearing = b->f_rbearing();
|
||||
last_rPadding = b->f_rpadding();
|
||||
last_rBearing = 0;// b->f_rbearing(); (0 for newline)
|
||||
last_rPadding = 0;// b->f_rpadding(); (0 for newline)
|
||||
|
||||
accumulate_max(maxWidth, width);
|
||||
width = (b->f_width() - last_rBearing);
|
||||
width = ppadding.left() + ppadding.right();
|
||||
// + (b->f_width() - last_rBearing); (0 for newline)
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -333,11 +488,14 @@ void String::recountNaturalSize(
|
|||
computeParagraphDirection(_text.size());
|
||||
}
|
||||
if (width > 0) {
|
||||
if (!lineHeight) lineHeight = CountBlockHeight(_blocks.back().get(), _st);
|
||||
_minHeight += lineHeight;
|
||||
if (!lineHeight) {
|
||||
lineHeight = CountBlockHeight(_blocks.back().get(), _st);
|
||||
}
|
||||
_minHeight += ppadding.top() + lineHeight + ppadding.bottom();
|
||||
accumulate_max(maxWidth, width);
|
||||
}
|
||||
_maxWidth = maxWidth.ceil().toInt();
|
||||
_endsWithParagraphDetails = (pindex != 0);
|
||||
}
|
||||
|
||||
int String::countMaxMonospaceWidth() const {
|
||||
|
|
@ -487,6 +645,17 @@ bool String::updateSkipBlock(int width, int height) {
|
|||
}
|
||||
_text.resize(block->position());
|
||||
_blocks.pop_back();
|
||||
} else if (_endsWithParagraphDetails) {
|
||||
_text.push_back(QChar::LineFeed);
|
||||
_blocks.push_back(Block::Newline(
|
||||
_st->font,
|
||||
_text,
|
||||
_text.size() - 1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0));
|
||||
_skipBlockAddedNewline = true;
|
||||
}
|
||||
_text.push_back('_');
|
||||
_blocks.push_back(Block::Skip(
|
||||
|
|
@ -504,9 +673,15 @@ bool String::updateSkipBlock(int width, int height) {
|
|||
bool String::removeSkipBlock() {
|
||||
if (_blocks.empty() || _blocks.back()->type() != TextBlockType::Skip) {
|
||||
return false;
|
||||
}
|
||||
} else if (_skipBlockAddedNewline) {
|
||||
_text.resize(_blocks.back()->position() - 1);
|
||||
_blocks.pop_back();
|
||||
_blocks.pop_back();
|
||||
_skipBlockAddedNewline = false;
|
||||
} else {
|
||||
_text.resize(_blocks.back()->position());
|
||||
_blocks.pop_back();
|
||||
}
|
||||
recountNaturalSize(false);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -558,24 +733,42 @@ void String::enumerateLines(
|
|||
int w,
|
||||
bool breakEverywhere,
|
||||
Callback callback) const {
|
||||
QFixed width = w;
|
||||
if (width < _minResizeWidth) width = _minResizeWidth;
|
||||
const auto width = QFixed(std::max(w, _minResizeWidth));
|
||||
|
||||
int lineHeight = 0;
|
||||
QFixed widthLeft = width, last_rBearing = 0, last_rPadding = 0;
|
||||
auto pindex = paragraphIndex(nullptr);
|
||||
auto paragraph = paragraphByIndex(pindex);
|
||||
auto ppadding = paragraphPadding(paragraph);
|
||||
auto widthLeft = width - ppadding.left() - ppadding.right();
|
||||
auto lineHeight = 0;
|
||||
auto last_rBearing = QFixed();
|
||||
auto last_rPadding = QFixed();
|
||||
bool longWordLine = true;
|
||||
for (auto &b : _blocks) {
|
||||
auto _btype = b->type();
|
||||
int blockHeight = CountBlockHeight(b.get(), _st);
|
||||
const auto blockHeight = CountBlockHeight(b.get(), _st);
|
||||
|
||||
if (_btype == TextBlockType::Newline) {
|
||||
if (!lineHeight) lineHeight = blockHeight;
|
||||
if (!lineHeight) {
|
||||
lineHeight = blockHeight;
|
||||
}
|
||||
lineHeight += ppadding.top();
|
||||
const auto index = paragraphIndex(b.get());
|
||||
if (pindex != index) {
|
||||
lineHeight += ppadding.bottom();
|
||||
pindex = index;
|
||||
paragraph = paragraphByIndex(pindex);
|
||||
ppadding = paragraphPadding(paragraph);
|
||||
} else {
|
||||
ppadding.setTop(0);
|
||||
}
|
||||
|
||||
callback(width - widthLeft, lineHeight);
|
||||
|
||||
lineHeight = 0;
|
||||
last_rBearing = b->f_rbearing();
|
||||
last_rPadding = b->f_rpadding();
|
||||
widthLeft = width - (b->f_width() - last_rBearing);
|
||||
last_rBearing = 0;// b->f_rbearing(); (0 for newline)
|
||||
last_rPadding = 0;// b->f_rpadding(); (0 for newline)
|
||||
widthLeft = width - ppadding.left() - ppadding.right();
|
||||
// - (b->f_width() - last_rBearing); (0 for newline)
|
||||
|
||||
longWordLine = true;
|
||||
continue;
|
||||
|
|
@ -636,12 +829,16 @@ void String::enumerateLines(
|
|||
j_width = (j->f_width() >= 0) ? j->f_width() : -j->f_width();
|
||||
}
|
||||
|
||||
callback(width - widthLeft, lineHeight);
|
||||
callback(width - widthLeft, lineHeight + ppadding.top());
|
||||
ppadding.setTop(0);
|
||||
|
||||
lineHeight = qMax(0, blockHeight);
|
||||
last_rBearing = j->f_rbearing();
|
||||
last_rPadding = j->f_rpadding();
|
||||
widthLeft = width - (j_width - last_rBearing);
|
||||
widthLeft = width
|
||||
- ppadding.left()
|
||||
- ppadding.right()
|
||||
- (j_width - last_rBearing);
|
||||
|
||||
longWordLine = !wordEndsHere;
|
||||
f = j + 1;
|
||||
|
|
@ -651,18 +848,24 @@ void String::enumerateLines(
|
|||
continue;
|
||||
}
|
||||
|
||||
callback(width - widthLeft, lineHeight);
|
||||
callback(width - widthLeft, lineHeight + ppadding.top());
|
||||
ppadding.setTop(0);
|
||||
|
||||
lineHeight = qMax(0, blockHeight);
|
||||
last_rBearing = b__f_rbearing;
|
||||
last_rPadding = b->f_rpadding();
|
||||
widthLeft = width - (b->f_width() - last_rBearing);
|
||||
widthLeft = width
|
||||
- ppadding.left()
|
||||
- ppadding.right()
|
||||
- (b->f_width() - last_rBearing);
|
||||
|
||||
longWordLine = true;
|
||||
continue;
|
||||
}
|
||||
if (widthLeft < width) {
|
||||
callback(width - widthLeft, lineHeight);
|
||||
callback(
|
||||
width - widthLeft,
|
||||
lineHeight + ppadding.top() + ppadding.bottom());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -852,14 +1055,42 @@ not_null<ExtendedData*> String::ensureExtended() {
|
|||
return _extended.get();
|
||||
}
|
||||
|
||||
uint16 String::countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const {
|
||||
uint16 String::countBlockEnd(
|
||||
const TextBlocks::const_iterator &i,
|
||||
const TextBlocks::const_iterator &e) const {
|
||||
return (i + 1 == e) ? _text.size() : (*(i + 1))->position();
|
||||
}
|
||||
|
||||
uint16 String::countBlockLength(const String::TextBlocks::const_iterator &i, const String::TextBlocks::const_iterator &e) const {
|
||||
uint16 String::countBlockLength(
|
||||
const TextBlocks::const_iterator &i,
|
||||
const TextBlocks::const_iterator &e) const {
|
||||
return countBlockEnd(i, e) - (*i)->position();
|
||||
}
|
||||
|
||||
ParagraphDetails *String::paragraphByIndex(int index) const {
|
||||
Expects(!index
|
||||
|| (_extended && index <= _extended->paragraphs.size()));
|
||||
|
||||
return index ? &_extended->paragraphs[index - 1] : nullptr;
|
||||
}
|
||||
|
||||
int String::paragraphIndex(const AbstractBlock *block) const {
|
||||
Expects(!block || block->type() == TextBlockType::Newline);
|
||||
|
||||
return block
|
||||
? static_cast<const NewlineBlock*>(block)->paragraphIndex()
|
||||
: _startParagraphIndex;
|
||||
}
|
||||
|
||||
QMargins String::paragraphPadding(ParagraphDetails *info) const {
|
||||
if (!info) {
|
||||
return {};
|
||||
}
|
||||
const auto skip = _st->blockVerticalSkip;
|
||||
const auto top = info->pre ? _st->blockHeader : 0;
|
||||
return _st->blockPadding + QMargins(0, top + skip, 0, skip);
|
||||
}
|
||||
|
||||
template <
|
||||
typename AppendPartCallback,
|
||||
typename ClickHandlerStartCallback,
|
||||
|
|
@ -994,8 +1225,8 @@ bool String::hasNotEmojiAndSpaces() const {
|
|||
return _hasNotEmojiAndSpaces;
|
||||
}
|
||||
|
||||
const base::flat_map<int, int> &String::modifications() const {
|
||||
static const auto kEmpty = base::flat_map<int, int>();
|
||||
const base::flat_map<int, Deltas> &String::modifications() const {
|
||||
static const auto kEmpty = base::flat_map<int, Deltas>();
|
||||
return _extended ? _extended->modifications : kEmpty;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,12 +76,18 @@ static constexpr TextSelection AllTextSelection = { 0, 0xFFFF };
|
|||
namespace Ui::Text {
|
||||
|
||||
struct Block;
|
||||
class AbstractBlock;
|
||||
struct IsolatedEmoji;
|
||||
struct OnlyCustomEmoji;
|
||||
struct SpoilerData;
|
||||
struct ParagraphDetails;
|
||||
struct ExtendedData;
|
||||
|
||||
struct Deltas {
|
||||
uint16 added = 0;
|
||||
uint16 removed = 0;
|
||||
};
|
||||
|
||||
struct StateRequest {
|
||||
enum class Flag {
|
||||
BreakEverywhere = (1 << 0),
|
||||
|
|
@ -157,6 +163,38 @@ struct GeometryDescriptor {
|
|||
bool elisionOneLine,
|
||||
bool elisionBreakEverywhere);
|
||||
|
||||
struct BlockPaintCache {
|
||||
QImage corners;
|
||||
QColor headerCached;
|
||||
QColor bgCached;
|
||||
QColor outlineCached;
|
||||
QColor iconCached;
|
||||
|
||||
QColor header;
|
||||
QColor bg;
|
||||
QColor outline;
|
||||
QColor icon;
|
||||
|
||||
const style::icon *topright = nullptr;
|
||||
QPoint toprightPosition;
|
||||
bool withHeader = false;
|
||||
};
|
||||
|
||||
void ValidateBlockPaintCache(
|
||||
BlockPaintCache &cache,
|
||||
const style::TextStyle &st);
|
||||
|
||||
struct SkipBlockPaintParts {
|
||||
bool skipTop : 1 = false;
|
||||
bool skipBottom : 1 = false;
|
||||
};
|
||||
void FillBlockPaint(
|
||||
QPainter &p,
|
||||
QRect rect,
|
||||
const BlockPaintCache &cache,
|
||||
const style::TextStyle &st,
|
||||
SkipBlockPaintParts parts = {});
|
||||
|
||||
struct PaintContext {
|
||||
QPoint position;
|
||||
int outerWidth = 0; // For automatic RTL Ui inversion.
|
||||
|
|
@ -166,6 +204,8 @@ struct PaintContext {
|
|||
QRect clip;
|
||||
|
||||
const style::TextPalette *palette = nullptr;
|
||||
BlockPaintCache *pre = nullptr;
|
||||
BlockPaintCache *blockquote = nullptr;
|
||||
std::span<SpecialColor> colors;
|
||||
SpoilerMessCache *spoiler = nullptr;
|
||||
crl::time now = 0;
|
||||
|
|
@ -301,7 +341,7 @@ public:
|
|||
[[nodiscard]] OnlyCustomEmoji toOnlyCustomEmoji() const;
|
||||
|
||||
[[nodiscard]] bool hasNotEmojiAndSpaces() const;
|
||||
[[nodiscard]] const base::flat_map<int, int> &modifications() const;
|
||||
[[nodiscard]] const base::flat_map<int, Deltas> &modifications() const;
|
||||
|
||||
[[nodiscard]] const style::TextStyle *style() const {
|
||||
return _st;
|
||||
|
|
@ -337,6 +377,11 @@ private:
|
|||
[[nodiscard]] uint16 countBlockLength(
|
||||
const TextBlocks::const_iterator &i,
|
||||
const TextBlocks::const_iterator &e) const;
|
||||
[[nodiscard]] ParagraphDetails *paragraphByIndex(int index) const;
|
||||
[[nodiscard]] QMargins paragraphPadding(ParagraphDetails *info) const;
|
||||
|
||||
// block must be either nullptr or a pointer to a NewlineBlock.
|
||||
[[nodiscard]] int paragraphIndex(const AbstractBlock *block) const;
|
||||
|
||||
// Template method for originalText(), originalTextWithEntities().
|
||||
template <
|
||||
|
|
@ -377,13 +422,15 @@ private:
|
|||
int _minResizeWidth = 0;
|
||||
int _maxWidth = 0;
|
||||
int _minHeight = 0;
|
||||
int16 _startParagraphIndex = 0;
|
||||
uint16 _startParagraphIndex = 0;
|
||||
bool _startParagraphLTR : 1 = false;
|
||||
bool _startParagraphRTL : 1 = false;
|
||||
bool _hasCustomEmoji : 1 = false;
|
||||
bool _isIsolatedEmoji : 1 = false;
|
||||
bool _isOnlyCustomEmoji : 1 = false;
|
||||
bool _hasNotEmojiAndSpaces : 1 = false;
|
||||
bool _skipBlockAddedNewline : 1 = false;
|
||||
bool _endsWithParagraphDetails : 1 = false;
|
||||
|
||||
friend class Parser;
|
||||
friend class Renderer;
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ public:
|
|||
uint16 linkIndex,
|
||||
uint16 colorIndex);
|
||||
|
||||
[[nodiscard]] int16 paragraphIndex() const {
|
||||
[[nodiscard]] uint16 paragraphIndex() const {
|
||||
return _paragraphIndex;
|
||||
}
|
||||
[[nodiscard]] Qt::LayoutDirection paragraphDirection() const {
|
||||
|
|
@ -115,7 +115,7 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
int16 _paragraphIndex = 0;
|
||||
uint16 _paragraphIndex = 0;
|
||||
bool _paragraphLTR : 1 = false;
|
||||
bool _paragraphRTL : 1 = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
namespace Ui::Text {
|
||||
|
||||
struct Deltas;
|
||||
|
||||
class String;
|
||||
|
||||
class SpoilerClickHandler final : public ClickHandler {
|
||||
|
|
@ -57,7 +59,7 @@ struct ExtendedData {
|
|||
std::vector<ClickHandlerPtr> links;
|
||||
std::vector<ParagraphDetails> paragraphs;
|
||||
std::unique_ptr<SpoilerData> spoiler;
|
||||
base::flat_map<int, int> modifications;
|
||||
base::flat_map<int, Deltas> modifications;
|
||||
};
|
||||
|
||||
} // namespace Ui::Text
|
||||
|
|
|
|||
|
|
@ -211,6 +211,8 @@ void Parser::createBlock(int32 skipBack) {
|
|||
push(&Block::Emoji, _emoji);
|
||||
} else if (newline) {
|
||||
push(&Block::Newline);
|
||||
auto &newline = _t->_blocks.back().unsafe<NewlineBlock>();
|
||||
newline._paragraphIndex = _paragraphIndex;
|
||||
} else {
|
||||
push(&Block::Text, _t->_minResizeWidth);
|
||||
}
|
||||
|
|
@ -231,7 +233,7 @@ void Parser::createNewlineBlock(bool fromOriginalText) {
|
|||
createBlock();
|
||||
}
|
||||
|
||||
void Parser::ensureAtNewline() {
|
||||
void Parser::ensureAtNewline(ParagraphDetails details) {
|
||||
createBlock();
|
||||
const auto lastType = _t->_blocks.empty()
|
||||
? TextBlockType::Newline
|
||||
|
|
@ -241,6 +243,16 @@ void Parser::ensureAtNewline() {
|
|||
createNewlineBlock(false);
|
||||
_customEmojiData = base::take(saved);
|
||||
}
|
||||
auto ¶graphs = _t->ensureExtended()->paragraphs;
|
||||
paragraphs.push_back(std::move(details));
|
||||
const auto index = _paragraphIndex = int(paragraphs.size());
|
||||
if (_t->_blocks.empty()) {
|
||||
_t->_startParagraphIndex = index;
|
||||
} else {
|
||||
auto &last = _t->_blocks.back();
|
||||
Assert(last->type() == TextBlockType::Newline);
|
||||
last.unsafe<NewlineBlock>()._paragraphIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::finishEntities() {
|
||||
|
|
@ -259,11 +271,18 @@ void Parser::finishEntities() {
|
|||
const auto lastType = _t->_blocks.empty()
|
||||
? TextBlockType::Newline
|
||||
: _t->_blocks.back()->type();
|
||||
if ((lastType != TextBlockType::Newline)
|
||||
&& ((*flags)
|
||||
if ((*flags)
|
||||
& (TextBlockFlag::Pre
|
||||
| TextBlockFlag::Blockquote))) {
|
||||
| TextBlockFlag::Blockquote)) {
|
||||
_paragraphIndex = 0;
|
||||
if (lastType != TextBlockType::Newline) {
|
||||
_newlineAwaited = true;
|
||||
} else if (_t->_blocks.empty()) {
|
||||
_t->_startParagraphIndex = 0;
|
||||
} else {
|
||||
auto &last = _t->_blocks.back();
|
||||
last.unsafe<NewlineBlock>()._paragraphIndex = 0;
|
||||
}
|
||||
}
|
||||
if (IsMono(*flags)) {
|
||||
_monoIndex = 0;
|
||||
|
|
@ -340,7 +359,10 @@ bool Parser::checkEntities() {
|
|||
flags = TextBlockFlag::Code;
|
||||
} else {
|
||||
flags = TextBlockFlag::Pre;
|
||||
ensureAtNewline();
|
||||
ensureAtNewline({
|
||||
.language = _waitingEntity->data(),
|
||||
.pre = true,
|
||||
});
|
||||
}
|
||||
const auto text = QString(entityBegin, entityLength);
|
||||
|
||||
|
|
@ -356,7 +378,7 @@ bool Parser::checkEntities() {
|
|||
}
|
||||
} else if (entityType == EntityType::Blockquote) {
|
||||
flags = TextBlockFlag::Blockquote;
|
||||
ensureAtNewline();
|
||||
ensureAtNewline({ .blockquote = true });
|
||||
} else if (entityType == EntityType::Url
|
||||
|| entityType == EntityType::Email
|
||||
|| entityType == EntityType::Mention
|
||||
|
|
@ -581,7 +603,12 @@ bool Parser::isLinkEntity(const EntityInText &entity) const {
|
|||
}
|
||||
|
||||
void Parser::updateModifications(int index, int delta) {
|
||||
_t->ensureExtended()->modifications[index] += delta;
|
||||
auto &deltas = _t->ensureExtended()->modifications[index];
|
||||
if (delta > 0) {
|
||||
deltas.added += delta;
|
||||
} else {
|
||||
deltas.removed -= delta;
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::parse(const TextParseOptions &options) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
namespace Ui::Text {
|
||||
|
||||
struct ParagraphDetails;
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
Parser(
|
||||
|
|
@ -58,7 +60,7 @@ private:
|
|||
void blockCreated();
|
||||
void createBlock(int32 skipBack = 0);
|
||||
void createNewlineBlock(bool fromOriginalText);
|
||||
void ensureAtNewline();
|
||||
void ensureAtNewline(ParagraphDetails details);
|
||||
|
||||
// Returns true if at least one entity was parsed in the current position.
|
||||
bool checkEntities();
|
||||
|
|
@ -113,6 +115,7 @@ private:
|
|||
uint16 _linkIndex = 0;
|
||||
uint16 _colorIndex = 0;
|
||||
uint16 _monoIndex = 0;
|
||||
uint16 _paragraphIndex = 0;
|
||||
EmojiPtr _emoji = nullptr; // current emoji, if current word is an emoji, or zero
|
||||
int32 _blockStart = 0; // offset in result, from which current parsed block is started
|
||||
int32 _diacritics = 0; // diacritic chars skipped without good char
|
||||
|
|
|
|||
|
|
@ -206,6 +206,8 @@ void Renderer::draw(QPainter &p, const PaintContext &context) {
|
|||
? (1. - _spoiler->revealAnimation.value(
|
||||
_spoiler->revealed ? 1. : 0.))
|
||||
: 0.;
|
||||
_preBlockCache = context.pre;
|
||||
_blockquoteBlockCache = context.blockquote;
|
||||
enumerate();
|
||||
}
|
||||
|
||||
|
|
@ -231,6 +233,7 @@ void Renderer::enumerate() {
|
|||
if ((*_t->_blocks.cbegin())->type() != TextBlockType::Newline) {
|
||||
initNextParagraph(
|
||||
_t->_blocks.cbegin(),
|
||||
_t->_startParagraphIndex,
|
||||
UnpackParagraphDirection(
|
||||
_t->_startParagraphLTR,
|
||||
_t->_startParagraphRTL));
|
||||
|
|
@ -259,6 +262,9 @@ void Renderer::enumerate() {
|
|||
if (!_lineHeight) {
|
||||
_lineHeight = blockHeight;
|
||||
}
|
||||
const auto pindex = static_cast<const NewlineBlock*>(b)->paragraphIndex();
|
||||
const auto changed = (_pindex != pindex);
|
||||
fillParagraphBg(changed ? _ppadding.bottom() : 0);
|
||||
if (!drawLine((*i)->position(), i, e)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -271,6 +277,7 @@ void Renderer::enumerate() {
|
|||
|
||||
initNextParagraph(
|
||||
i + 1,
|
||||
pindex,
|
||||
static_cast<const NewlineBlock*>(b)->paragraphDirection());
|
||||
|
||||
longWordLine = true;
|
||||
|
|
@ -340,6 +347,7 @@ void Renderer::enumerate() {
|
|||
: (j + 1 != en)
|
||||
? (j + 1)->position()
|
||||
: _t->countBlockEnd(i, e);
|
||||
fillParagraphBg(0);
|
||||
if (!drawLine(lineEnd, i, e)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -368,6 +376,7 @@ void Renderer::enumerate() {
|
|||
const auto lineEnd = !_elidedLine
|
||||
? b->position()
|
||||
: _t->countBlockEnd(i, e);
|
||||
fillParagraphBg(0);
|
||||
if (!drawLine(lineEnd, i, e)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -386,6 +395,7 @@ void Renderer::enumerate() {
|
|||
continue;
|
||||
}
|
||||
if (_lineStart < _t->_text.size()) {
|
||||
fillParagraphBg(_ppadding.bottom());
|
||||
if (!drawLine(_t->_text.size(), e, e)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -396,6 +406,37 @@ void Renderer::enumerate() {
|
|||
}
|
||||
}
|
||||
|
||||
void Renderer::fillParagraphBg(int paddingBottom) {
|
||||
const auto cache = (!_p || !_paragraph)
|
||||
? nullptr
|
||||
: _paragraph->pre
|
||||
? _preBlockCache
|
||||
: _paragraph->blockquote
|
||||
? _blockquoteBlockCache
|
||||
: nullptr;
|
||||
if (cache) {
|
||||
auto &valid = _paragraph->pre
|
||||
? _preBlockCacheValid
|
||||
: _blockquoteBlockCacheValid;
|
||||
if (!valid) {
|
||||
valid = true;
|
||||
ValidateBlockPaintCache(*cache, *_t->_st);
|
||||
}
|
||||
const auto skip = _t->_st->blockVerticalSkip;
|
||||
const auto isTop = (_y != _blockLineTop);
|
||||
const auto isBottom = (paddingBottom != 0);
|
||||
const auto top = _blockLineTop + (isTop ? skip : 0);
|
||||
const auto fill = _y + _lineHeight + paddingBottom - top
|
||||
- (isBottom ? skip : 0);
|
||||
const auto rect = QRect(_startLeft, top, _startLineWidth, fill);
|
||||
FillBlockPaint(*_p, rect, *cache, *_t->_st, {
|
||||
.skipTop = !isTop,
|
||||
.skipBottom = !isBottom,
|
||||
});
|
||||
}
|
||||
_blockLineTop = _y + _lineHeight + paddingBottom;
|
||||
}
|
||||
|
||||
StateResult Renderer::getState(
|
||||
QPoint point,
|
||||
GeometryDescriptor geometry,
|
||||
|
|
@ -430,12 +471,22 @@ crl::time Renderer::now() const {
|
|||
|
||||
void Renderer::initNextParagraph(
|
||||
String::TextBlocks::const_iterator i,
|
||||
int16 paragraphIndex,
|
||||
Qt::LayoutDirection direction) {
|
||||
_parDirection = (direction == Qt::LayoutDirectionAuto)
|
||||
? style::LayoutDirection()
|
||||
: direction;
|
||||
_parStartBlock = i;
|
||||
_paragraphWidthRemaining = 0;
|
||||
if (_pindex != paragraphIndex) {
|
||||
_y += _ppadding.bottom();
|
||||
_pindex = paragraphIndex;
|
||||
_paragraph = _t->paragraphByIndex(paragraphIndex);
|
||||
_ppadding = _t->paragraphPadding(_paragraph);
|
||||
_blockLineTop = _y;
|
||||
_y += _ppadding.top();
|
||||
_ppadding.setTop(0);
|
||||
}
|
||||
const auto e = _t->_blocks.cend();
|
||||
if (i == e) {
|
||||
_lineStart = _parStart = _t->_text.size();
|
||||
|
|
@ -461,6 +512,7 @@ void Renderer::initNextParagraph(
|
|||
_parLength = ((i == e) ? _t->_text.size() : (*i)->position()) - _parStart;
|
||||
}
|
||||
_parAnalysis.resize(0);
|
||||
_paragraphWidthRemaining += _ppadding.left() + _ppadding.right();
|
||||
initNextLine();
|
||||
}
|
||||
|
||||
|
|
@ -470,9 +522,12 @@ void Renderer::initNextLine() {
|
|||
.top = (_y - _startTop),
|
||||
.width = _paragraphWidthRemaining.ceil().toInt(),
|
||||
});
|
||||
_x = _startLeft + line.left;
|
||||
_blockLineTop += _startTop + line.top - _y;
|
||||
_x = _startLeft + line.left + _ppadding.left();
|
||||
_y = _startTop + line.top;
|
||||
_lineWidth = _wLeft = line.width;
|
||||
_startLineWidth = line.width;
|
||||
_lineWidth = _startLineWidth - _ppadding.left() - _ppadding.right();
|
||||
_wLeft = _lineWidth;
|
||||
_elidedLine = line.elided;
|
||||
}
|
||||
|
||||
|
|
@ -1252,7 +1307,7 @@ void Renderer::prepareElidedLine(QString &lineText, int32 lineStart, int32 &line
|
|||
eShapeLine(line);
|
||||
|
||||
auto elideWidth = _f->elidew;
|
||||
_wLeft = _lineWidth - elideWidth;
|
||||
_wLeft = _lineWidth - _ppadding.left() - _ppadding.right() - elideWidth;
|
||||
|
||||
int firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1);
|
||||
int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0, i;
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ private:
|
|||
[[nodiscard]] crl::time now() const;
|
||||
void initNextParagraph(
|
||||
String::TextBlocks::const_iterator i,
|
||||
int16 paragraphIndex,
|
||||
Qt::LayoutDirection direction);
|
||||
void initNextLine();
|
||||
void initParagraphBidi();
|
||||
|
|
@ -89,6 +90,8 @@ private:
|
|||
int repeat = 0);
|
||||
void restoreAfterElided();
|
||||
|
||||
void fillParagraphBg(int paddingBottom);
|
||||
|
||||
// COPIED FROM qtextengine.cpp AND MODIFIED
|
||||
static void eAppendItems(
|
||||
QScriptAnalysis *analysis,
|
||||
|
|
@ -154,12 +157,21 @@ private:
|
|||
int _parLength = 0;
|
||||
bool _parHasBidi = false;
|
||||
QVarLengthArray<QScriptAnalysis, 4096> _parAnalysis;
|
||||
ParagraphDetails *_paragraph = nullptr;
|
||||
int _pindex = 0;
|
||||
QMargins _ppadding;
|
||||
int _blockLineTop = 0;
|
||||
BlockPaintCache *_preBlockCache = nullptr;
|
||||
BlockPaintCache *_blockquoteBlockCache = nullptr;
|
||||
bool _preBlockCacheValid = false;
|
||||
bool _blockquoteBlockCacheValid = false;
|
||||
|
||||
// current line data
|
||||
QTextEngine *_e = nullptr;
|
||||
style::font _f;
|
||||
int _startLeft = 0;
|
||||
int _startTop = 0;
|
||||
int _startLineWidth = 0;
|
||||
QFixed _x, _wLeft, _last_rPadding;
|
||||
int _y = 0;
|
||||
int _yDelta = 0;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue