diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 764d01b..6dd1d8d 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -95,24 +95,13 @@ SpoilerMessCache::SpoilerMessCache(int capacity) : _capacity(capacity) { not_null SpoilerMessCache::lookup(QColor color) { for (auto &entry : _cache) { if (entry.color == color) { - entry.generation = ++_generation; return &entry.mess; } } - if (_cache.size() == _capacity) { - const auto i = ranges::min_element( - _cache, - ranges::less(), - &Entry::generation); - i->generation = ++_generation; - i->color = color; - i->mess = Ui::SpoilerMessCached(DefaultTextSpoilerMask(), color); - return &i->mess; - } + Assert(_cache.size() < _capacity); _cache.push_back({ .mess = Ui::SpoilerMessCached(DefaultTextSpoilerMask(), color), .color = color, - .generation = ++_generation, }); return &_cache.back().mess; } diff --git a/ui/text/text.h b/ui/text/text.h index 47de231..090a39c 100644 --- a/ui/text/text.h +++ b/ui/text/text.h @@ -115,12 +115,10 @@ private: struct Entry { SpoilerMessCached mess; QColor color; - int generation = 0; }; std::vector _cache; const int _capacity = 0; - int _generation = 0; }; diff --git a/ui/text/text_renderer.cpp b/ui/text/text_renderer.cpp index c29e896..7b5033b 100644 --- a/ui/text/text_renderer.cpp +++ b/ui/text/text_renderer.cpp @@ -61,8 +61,31 @@ void InitTextItemWithScriptItem(QTextItemInt &ti, const QScriptItem &si) { ti.flags |= QTextItem::StrikeOut; } -} // namespace +void AppendRange( + QVarLengthArray &ranges, + FixedRange range) { + for (auto i = ranges.begin(); i != ranges.end(); ++i) { + if (range.till < i->from) { + ranges.insert(i, range); + return; + } else if (!Distinct(range, *i)) { + *i = United(*i, range); + for (auto j = i + 1; j != ranges.end(); ++j) { + if (j->from > i->till) { + ranges.erase(i + 1, j); + return; + } else { + *i = United(*i, *j); + } + } + ranges.erase(i + 1, ranges.end()); + return; + } + } + ranges.push_back(range); +} +} // namespace struct Renderer::BidiControl { inline BidiControl(bool rtl) @@ -109,6 +132,28 @@ struct Renderer::BidiControl { bool override = false; }; +FixedRange Intersected(FixedRange a, FixedRange b) { + return { + .from = std::max(a.from, b.from), + .till = std::min(a.till, b.till), + }; +} + +bool Intersects(FixedRange a, FixedRange b) { + return (a.till > b.from) && (b.till > a.from); +} + +FixedRange United(FixedRange a, FixedRange b) { + return { + .from = std::min(a.from, b.from), + .till = std::max(a.till, b.till), + }; +} + +bool Distinct(FixedRange a, FixedRange b) { + return (a.till < b.from) || (b.till < a.from); +} + Renderer::Renderer(const Ui::Text::String &t) : _t(&t) { } @@ -194,6 +239,12 @@ void Renderer::enumerate() { auto last_rBearing = QFixed(0); _last_rPadding = QFixed(0); + const auto guard = gsl::finally([&] { + if (_p) { + paintSpoilerRects(); + } + }); + auto blockIndex = 0; bool longWordLine = true; auto e = _t->_blocks.cend(); @@ -572,13 +623,13 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato if ((selectFromStart && _parDirection == Qt::LeftToRight) || (selectTillEnd && _parDirection == Qt::RightToLeft)) { if (x > _x) { - fillSelectRange(_x, x); + fillSelectRange({ _x, x }); } } if ((selectTillEnd && _parDirection == Qt::LeftToRight) || (selectFromStart && _parDirection == Qt::RightToLeft)) { if (x < _x + _wLeft) { - fillSelectRange(x + _w - _wLeft, _x + _w); + fillSelectRange({ x + _w - _wLeft, _x + _w }); } } } @@ -730,14 +781,8 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato if (rtl) { glyphX += spacesWidth; } - struct { - QFixed from; - QFixed to; - } fillSelect; - struct { - QFixed from; - QFixed width; - } fillSpoiler; + FixedRange fillSelect; + FixedRange fillSpoiler; if (_background.selectActiveBlock) { fillSelect = { x, x + si.width }; } else if (_localFrom + si.position < _selection.to) { @@ -757,18 +802,15 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato } } } - const auto hasSpoiler = _background.spoiler && - (_background.inFront || _background.startMs); + const auto hasSpoiler = _background.inFront + || _background.startMs; if (hasSpoiler) { - fillSpoiler = { x, si.width }; + fillSpoiler = { x, x + si.width }; } const auto spoilerOpacity = hasSpoiler ? fillSpoilerOpacity() : 0.; - const auto hasSelect = fillSelect.to != QFixed(); - if (hasSelect) { - fillSelectRange(fillSelect.from, fillSelect.to); - } + fillSelectRange(fillSelect); const auto opacity = _p->opacity(); if (spoilerOpacity < 1.) { if (hasSpoiler) { @@ -795,16 +837,12 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato .paused = _paused, }); } + if (hasSpoiler) { + _p->setOpacity(opacity); + } } if (hasSpoiler) { - _p->setOpacity(opacity * spoilerOpacity); - fillSpoilerRange( - fillSpoiler.from, - fillSpoiler.width, - blockIndex, - currentBlock->from(), - (nextBlock ? nextBlock->from() : _t->_text.size())); - _p->setOpacity(opacity); + pushSpoilerRange(fillSpoiler, fillSelect, blockIndex); } //} else if (_p && currentBlock->type() == TextBlockSkip) { // debug // _p->fillRect(QRect(x.toInt(), _y, currentBlock->width(), static_cast(currentBlock)->height()), QColor(0, 0, 0, 32)); @@ -896,11 +934,14 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato gf.justified = false; InitTextItemWithScriptItem(gf, si); + auto itemRange = FixedRange{ x, x + itemWidth }; + auto fillSelect = FixedRange(); auto hasSelected = false; auto hasNotSelected = true; auto selectedRect = QRect(); if (_background.selectActiveBlock) { - fillSelectRange(x, x + itemWidth); + fillSelect = itemRange; + fillSelectRange(fillSelect); } else if (_localFrom + itemStart < _selection.to && _localFrom + itemEnd > _selection.from) { hasSelected = true; auto selX = x; @@ -943,7 +984,8 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato } if (rtl) selX = x + itemWidth - (selX - x) - selWidth; selectedRect = QRect(selX.toInt(), _y + _yDelta, (selX + selWidth).toInt() - selX.toInt(), _fontHeight); - fillSelectRange(selX, selX + selWidth); + fillSelect = { selX, selX + selWidth }; + fillSelectRange(fillSelect); } const auto hasSpoiler = (_background.inFront || _background.startMs); const auto spoilerOpacity = hasSpoiler @@ -1024,29 +1066,28 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato } else { _p->setClipping(false); } + } else if (hasSpoiler && !isElidedBlock) { + _p->setOpacity(opacity); } } if (hasSpoiler) { - _p->setOpacity(opacity * spoilerOpacity); - fillSpoilerRange( - x, - itemWidth, - blockIndex, - _localFrom + itemStart, - _localFrom + itemEnd); - _p->setOpacity(opacity); + pushSpoilerRange(itemRange, fillSelect, blockIndex); } } x += itemWidth; } + fillSpoilerRects(); return true; } -void Renderer::fillSelectRange(QFixed from, QFixed to) { - auto left = from.toInt(); - auto width = to.toInt() - left; +void Renderer::fillSelectRange(FixedRange range) { + if (range.empty()) { + return; + } + const auto left = range.from.toInt(); + const auto width = range.till.toInt() - left; _p->fillRect(left, _y + _yDelta, width, _fontHeight, _palette->selectBg); } @@ -1066,37 +1107,89 @@ float64 Renderer::fillSpoilerOpacity() { return (1. - std::min(progress, 1.)); } -void Renderer::fillSpoilerRange( - QFixed x, - QFixed width, - int currentBlockIndex, - int positionFrom, - int positionTill) { +void Renderer::pushSpoilerRange( + FixedRange range, + FixedRange selected, + int currentBlockIndex) { if (!_background.spoiler || !_t->_spoiler) { return; } const auto elided = (_indexOfElidedBlock == currentBlockIndex) ? (_elideRemoveFromEnd + _f->elidew) : 0; - const auto left = x.toInt(); - const auto useWidth = ((x + width).toInt() - left) - elided; - if (useWidth <= 0) { + range.till -= elided; + if (range.empty()) { + return; + } else if (selected.empty() || !Intersects(range, selected)) { + AppendRange(_spoilerRanges, range); + } else { + AppendRange(_spoilerRanges, { range.from, selected.from }); + AppendRange(_spoilerSelectedRanges, Intersected(range, selected)); + AppendRange(_spoilerRanges, { selected.till, range.till }); + } +} + +void Renderer::fillSpoilerRects() { + fillSpoilerRects(_spoilerRects, _spoilerRanges); + fillSpoilerRects(_spoilerSelectedRects, _spoilerSelectedRanges); +} + +void Renderer::fillSpoilerRects( + QVarLengthArray &rects, + QVarLengthArray &ranges) { + if (ranges.empty()) { return; } - const auto rect = QRect( - left, - _y + _yDelta, - useWidth, - _fontHeight); + auto lastTill = ranges.front().from.toInt() - 1; + const auto y = _y + _yDelta; + for (const auto &range : ranges) { + auto from = range.from.toInt(); + auto till = range.till.toInt(); + if (from <= lastTill) { + auto &last = rects.back(); + from = std::min(from, last.x()); + till = std::max(till, last.x() + last.width()); + last = { from, y, till - from, _fontHeight }; + } else { + rects.push_back({ from, y, till - from, _fontHeight }); + } + lastTill = till; + } + ranges.clear(); +} + +void Renderer::paintSpoilerRects() { + if (!_t->_spoiler) { + return; + } + const auto index = _t->_spoiler->animation.index(now(), _paused); + paintSpoilerRects( + _spoilerRects, + _palette->spoilerFg, + index); + paintSpoilerRects( + _spoilerSelectedRects, + _palette->selectSpoilerFg, + index); +} + +void Renderer::paintSpoilerRects( + const QVarLengthArray &rects, + const style::color &color, + int index) { + if (rects.empty()) { + return; + } + const auto frame = _spoilerCache->lookup(color->c)->frame(index); if (_spoilerCache) { - const auto mess = _spoilerCache->lookup((*_background.spoiler)->c); - const auto spoiler = _t->_spoiler.get(); - const auto frame = mess->frame( - spoiler->animation.index(now(), _paused)); - Ui::FillSpoilerRect(*_p, rect, frame, -rect.topLeft()); + for (const auto &rect : rects) { + Ui::FillSpoilerRect(*_p, rect, frame, -rect.topLeft()); + } } else { // Show forgotten spoiler context part. - _p->fillRect(rect, Qt::red); + for (const auto &rect : rects) { + _p->fillRect(rect, Qt::red); + } } } @@ -1920,8 +2013,7 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) { = _t->_spoiler->links.at(block->spoilerIndex() - 1); const auto inBack = (handler && handler->shown()); _background.inFront = !inBack; - _background.spoiler = &_palette->spoilerFg; - _background.spoilerSelected = &_palette->selectSpoilerFg; + _background.spoiler = true; _background.startMs = handler ? handler->startMs() : 0; _background.spoilerIndex = block->spoilerIndex(); } diff --git a/ui/text/text_renderer.h b/ui/text/text_renderer.h index 48f4e30..e904c14 100644 --- a/ui/text/text_renderer.h +++ b/ui/text/text_renderer.h @@ -15,6 +15,20 @@ struct QScriptLine; namespace Ui::Text { +struct FixedRange { + QFixed from; + QFixed till; + + [[nodiscard]] bool empty() const { + return (till <= from); + } +}; + +[[nodiscard]] FixedRange Intersected(FixedRange a, FixedRange b); +[[nodiscard]] bool Intersects(FixedRange a, FixedRange b); +[[nodiscard]] FixedRange United(FixedRange a, FixedRange b); +[[nodiscard]] bool Distinct(FixedRange a, FixedRange b); + class Renderer final { public: explicit Renderer(const Ui::Text::String &t); @@ -31,6 +45,8 @@ public: StateRequestElided request); private: + static constexpr int kSpoilersRectsSize = 512; + struct BidiControl; void enumerate(); @@ -42,14 +58,21 @@ private: uint16 _lineEnd, const String::TextBlocks::const_iterator &_endBlockIter, const String::TextBlocks::const_iterator &_end); - void fillSelectRange(QFixed from, QFixed to); + void fillSelectRange(FixedRange range); [[nodiscard]] float64 fillSpoilerOpacity(); - void fillSpoilerRange( - QFixed x, - QFixed width, - int currentBlockIndex, - int positionFrom, - int positionTill); + void pushSpoilerRange( + FixedRange range, + FixedRange selected, + int currentBlockIndex); + void fillSpoilerRects(); + void fillSpoilerRects( + QVarLengthArray &rects, + QVarLengthArray &ranges); + void paintSpoilerRects(); + void paintSpoilerRects( + const QVarLengthArray &rects, + const style::color &color, + int index); void elideSaveBlock( int32 blockIndex, const AbstractBlock *&_endBlock, @@ -100,9 +123,8 @@ private: const QPen *_currentPen = nullptr; const QPen *_currentPenSelected = nullptr; struct { - const style::color *spoiler = nullptr; - const style::color *spoilerSelected = nullptr; bool inFront = false; + bool spoiler = true; crl::time startMs = 0; uint16 spoilerIndex = 0; @@ -115,6 +137,10 @@ private: bool _fullWidthSelection = true; const QChar *_str = nullptr; mutable crl::time _cachedNow = 0; + QVarLengthArray _spoilerRanges; + QVarLengthArray _spoilerSelectedRanges; + QVarLengthArray _spoilerRects; + QVarLengthArray _spoilerSelectedRects; int _customEmojiSize = 0; int _customEmojiSkip = 0; @@ -122,7 +148,7 @@ private: // current paragraph data String::TextBlocks::const_iterator _parStartBlock; - Qt::LayoutDirection _parDirection; + Qt::LayoutDirection _parDirection = Qt::LayoutDirectionAuto; int _parStart = 0; int _parLength = 0; bool _parHasBidi = false;