Render correct spoiler selections.

This commit is contained in:
John Preston 2022-09-18 15:17:19 +04:00
parent 4ff5bc5773
commit a60fe582ad
4 changed files with 190 additions and 85 deletions

View file

@ -95,24 +95,13 @@ SpoilerMessCache::SpoilerMessCache(int capacity) : _capacity(capacity) {
not_null<SpoilerMessCached*> SpoilerMessCache::lookup(QColor color) { not_null<SpoilerMessCached*> SpoilerMessCache::lookup(QColor color) {
for (auto &entry : _cache) { for (auto &entry : _cache) {
if (entry.color == color) { if (entry.color == color) {
entry.generation = ++_generation;
return &entry.mess; return &entry.mess;
} }
} }
if (_cache.size() == _capacity) { Assert(_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;
}
_cache.push_back({ _cache.push_back({
.mess = Ui::SpoilerMessCached(DefaultTextSpoilerMask(), color), .mess = Ui::SpoilerMessCached(DefaultTextSpoilerMask(), color),
.color = color, .color = color,
.generation = ++_generation,
}); });
return &_cache.back().mess; return &_cache.back().mess;
} }

View file

@ -115,12 +115,10 @@ private:
struct Entry { struct Entry {
SpoilerMessCached mess; SpoilerMessCached mess;
QColor color; QColor color;
int generation = 0;
}; };
std::vector<Entry> _cache; std::vector<Entry> _cache;
const int _capacity = 0; const int _capacity = 0;
int _generation = 0;
}; };

View file

@ -61,8 +61,31 @@ void InitTextItemWithScriptItem(QTextItemInt &ti, const QScriptItem &si) {
ti.flags |= QTextItem::StrikeOut; ti.flags |= QTextItem::StrikeOut;
} }
} // namespace void AppendRange(
QVarLengthArray<FixedRange> &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 { struct Renderer::BidiControl {
inline BidiControl(bool rtl) inline BidiControl(bool rtl)
@ -109,6 +132,28 @@ struct Renderer::BidiControl {
bool override = false; 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) Renderer::Renderer(const Ui::Text::String &t)
: _t(&t) { : _t(&t) {
} }
@ -194,6 +239,12 @@ void Renderer::enumerate() {
auto last_rBearing = QFixed(0); auto last_rBearing = QFixed(0);
_last_rPadding = QFixed(0); _last_rPadding = QFixed(0);
const auto guard = gsl::finally([&] {
if (_p) {
paintSpoilerRects();
}
});
auto blockIndex = 0; auto blockIndex = 0;
bool longWordLine = true; bool longWordLine = true;
auto e = _t->_blocks.cend(); auto e = _t->_blocks.cend();
@ -572,13 +623,13 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
if ((selectFromStart && _parDirection == Qt::LeftToRight) if ((selectFromStart && _parDirection == Qt::LeftToRight)
|| (selectTillEnd && _parDirection == Qt::RightToLeft)) { || (selectTillEnd && _parDirection == Qt::RightToLeft)) {
if (x > _x) { if (x > _x) {
fillSelectRange(_x, x); fillSelectRange({ _x, x });
} }
} }
if ((selectTillEnd && _parDirection == Qt::LeftToRight) if ((selectTillEnd && _parDirection == Qt::LeftToRight)
|| (selectFromStart && _parDirection == Qt::RightToLeft)) { || (selectFromStart && _parDirection == Qt::RightToLeft)) {
if (x < _x + _wLeft) { 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) { if (rtl) {
glyphX += spacesWidth; glyphX += spacesWidth;
} }
struct { FixedRange fillSelect;
QFixed from; FixedRange fillSpoiler;
QFixed to;
} fillSelect;
struct {
QFixed from;
QFixed width;
} fillSpoiler;
if (_background.selectActiveBlock) { if (_background.selectActiveBlock) {
fillSelect = { x, x + si.width }; fillSelect = { x, x + si.width };
} else if (_localFrom + si.position < _selection.to) { } 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 && const auto hasSpoiler = _background.inFront
(_background.inFront || _background.startMs); || _background.startMs;
if (hasSpoiler) { if (hasSpoiler) {
fillSpoiler = { x, si.width }; fillSpoiler = { x, x + si.width };
} }
const auto spoilerOpacity = hasSpoiler const auto spoilerOpacity = hasSpoiler
? fillSpoilerOpacity() ? fillSpoilerOpacity()
: 0.; : 0.;
const auto hasSelect = fillSelect.to != QFixed(); fillSelectRange(fillSelect);
if (hasSelect) {
fillSelectRange(fillSelect.from, fillSelect.to);
}
const auto opacity = _p->opacity(); const auto opacity = _p->opacity();
if (spoilerOpacity < 1.) { if (spoilerOpacity < 1.) {
if (hasSpoiler) { if (hasSpoiler) {
@ -795,16 +837,12 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
.paused = _paused, .paused = _paused,
}); });
} }
if (hasSpoiler) {
_p->setOpacity(opacity);
}
} }
if (hasSpoiler) { if (hasSpoiler) {
_p->setOpacity(opacity * spoilerOpacity); pushSpoilerRange(fillSpoiler, fillSelect, blockIndex);
fillSpoilerRange(
fillSpoiler.from,
fillSpoiler.width,
blockIndex,
currentBlock->from(),
(nextBlock ? nextBlock->from() : _t->_text.size()));
_p->setOpacity(opacity);
} }
//} else if (_p && currentBlock->type() == TextBlockSkip) { // debug //} else if (_p && currentBlock->type() == TextBlockSkip) { // debug
// _p->fillRect(QRect(x.toInt(), _y, currentBlock->width(), static_cast<SkipBlock*>(currentBlock)->height()), QColor(0, 0, 0, 32)); // _p->fillRect(QRect(x.toInt(), _y, currentBlock->width(), static_cast<SkipBlock*>(currentBlock)->height()), QColor(0, 0, 0, 32));
@ -896,11 +934,14 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
gf.justified = false; gf.justified = false;
InitTextItemWithScriptItem(gf, si); InitTextItemWithScriptItem(gf, si);
auto itemRange = FixedRange{ x, x + itemWidth };
auto fillSelect = FixedRange();
auto hasSelected = false; auto hasSelected = false;
auto hasNotSelected = true; auto hasNotSelected = true;
auto selectedRect = QRect(); auto selectedRect = QRect();
if (_background.selectActiveBlock) { if (_background.selectActiveBlock) {
fillSelectRange(x, x + itemWidth); fillSelect = itemRange;
fillSelectRange(fillSelect);
} else if (_localFrom + itemStart < _selection.to && _localFrom + itemEnd > _selection.from) { } else if (_localFrom + itemStart < _selection.to && _localFrom + itemEnd > _selection.from) {
hasSelected = true; hasSelected = true;
auto selX = x; 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; if (rtl) selX = x + itemWidth - (selX - x) - selWidth;
selectedRect = QRect(selX.toInt(), _y + _yDelta, (selX + selWidth).toInt() - selX.toInt(), _fontHeight); 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 hasSpoiler = (_background.inFront || _background.startMs);
const auto spoilerOpacity = hasSpoiler const auto spoilerOpacity = hasSpoiler
@ -1024,29 +1066,28 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
} else { } else {
_p->setClipping(false); _p->setClipping(false);
} }
} else if (hasSpoiler && !isElidedBlock) {
_p->setOpacity(opacity);
} }
} }
if (hasSpoiler) { if (hasSpoiler) {
_p->setOpacity(opacity * spoilerOpacity); pushSpoilerRange(itemRange, fillSelect, blockIndex);
fillSpoilerRange(
x,
itemWidth,
blockIndex,
_localFrom + itemStart,
_localFrom + itemEnd);
_p->setOpacity(opacity);
} }
} }
x += itemWidth; x += itemWidth;
} }
fillSpoilerRects();
return true; return true;
} }
void Renderer::fillSelectRange(QFixed from, QFixed to) { void Renderer::fillSelectRange(FixedRange range) {
auto left = from.toInt(); if (range.empty()) {
auto width = to.toInt() - left; return;
}
const auto left = range.from.toInt();
const auto width = range.till.toInt() - left;
_p->fillRect(left, _y + _yDelta, width, _fontHeight, _palette->selectBg); _p->fillRect(left, _y + _yDelta, width, _fontHeight, _palette->selectBg);
} }
@ -1066,39 +1107,91 @@ float64 Renderer::fillSpoilerOpacity() {
return (1. - std::min(progress, 1.)); return (1. - std::min(progress, 1.));
} }
void Renderer::fillSpoilerRange( void Renderer::pushSpoilerRange(
QFixed x, FixedRange range,
QFixed width, FixedRange selected,
int currentBlockIndex, int currentBlockIndex) {
int positionFrom,
int positionTill) {
if (!_background.spoiler || !_t->_spoiler) { if (!_background.spoiler || !_t->_spoiler) {
return; return;
} }
const auto elided = (_indexOfElidedBlock == currentBlockIndex) const auto elided = (_indexOfElidedBlock == currentBlockIndex)
? (_elideRemoveFromEnd + _f->elidew) ? (_elideRemoveFromEnd + _f->elidew)
: 0; : 0;
const auto left = x.toInt(); range.till -= elided;
const auto useWidth = ((x + width).toInt() - left) - elided; if (range.empty()) {
if (useWidth <= 0) { 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<QRect, kSpoilersRectsSize> &rects,
QVarLengthArray<FixedRange> &ranges) {
if (ranges.empty()) {
return; return;
} }
const auto rect = QRect( auto lastTill = ranges.front().from.toInt() - 1;
left, const auto y = _y + _yDelta;
_y + _yDelta, for (const auto &range : ranges) {
useWidth, auto from = range.from.toInt();
_fontHeight); 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<QRect, kSpoilersRectsSize> &rects,
const style::color &color,
int index) {
if (rects.empty()) {
return;
}
const auto frame = _spoilerCache->lookup(color->c)->frame(index);
if (_spoilerCache) { if (_spoilerCache) {
const auto mess = _spoilerCache->lookup((*_background.spoiler)->c); for (const auto &rect : rects) {
const auto spoiler = _t->_spoiler.get();
const auto frame = mess->frame(
spoiler->animation.index(now(), _paused));
Ui::FillSpoilerRect(*_p, rect, frame, -rect.topLeft()); Ui::FillSpoilerRect(*_p, rect, frame, -rect.topLeft());
}
} else { } else {
// Show forgotten spoiler context part. // Show forgotten spoiler context part.
for (const auto &rect : rects) {
_p->fillRect(rect, Qt::red); _p->fillRect(rect, Qt::red);
} }
} }
}
void Renderer::elideSaveBlock(int32 blockIndex, const AbstractBlock *&_endBlock, int32 elideStart, int32 elideWidth) { void Renderer::elideSaveBlock(int32 blockIndex, const AbstractBlock *&_endBlock, int32 elideStart, int32 elideWidth) {
if (_elideSavedBlock) { if (_elideSavedBlock) {
@ -1920,8 +2013,7 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) {
= _t->_spoiler->links.at(block->spoilerIndex() - 1); = _t->_spoiler->links.at(block->spoilerIndex() - 1);
const auto inBack = (handler && handler->shown()); const auto inBack = (handler && handler->shown());
_background.inFront = !inBack; _background.inFront = !inBack;
_background.spoiler = &_palette->spoilerFg; _background.spoiler = true;
_background.spoilerSelected = &_palette->selectSpoilerFg;
_background.startMs = handler ? handler->startMs() : 0; _background.startMs = handler ? handler->startMs() : 0;
_background.spoilerIndex = block->spoilerIndex(); _background.spoilerIndex = block->spoilerIndex();
} }

View file

@ -15,6 +15,20 @@ struct QScriptLine;
namespace Ui::Text { 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 { class Renderer final {
public: public:
explicit Renderer(const Ui::Text::String &t); explicit Renderer(const Ui::Text::String &t);
@ -31,6 +45,8 @@ public:
StateRequestElided request); StateRequestElided request);
private: private:
static constexpr int kSpoilersRectsSize = 512;
struct BidiControl; struct BidiControl;
void enumerate(); void enumerate();
@ -42,14 +58,21 @@ private:
uint16 _lineEnd, uint16 _lineEnd,
const String::TextBlocks::const_iterator &_endBlockIter, const String::TextBlocks::const_iterator &_endBlockIter,
const String::TextBlocks::const_iterator &_end); const String::TextBlocks::const_iterator &_end);
void fillSelectRange(QFixed from, QFixed to); void fillSelectRange(FixedRange range);
[[nodiscard]] float64 fillSpoilerOpacity(); [[nodiscard]] float64 fillSpoilerOpacity();
void fillSpoilerRange( void pushSpoilerRange(
QFixed x, FixedRange range,
QFixed width, FixedRange selected,
int currentBlockIndex, int currentBlockIndex);
int positionFrom, void fillSpoilerRects();
int positionTill); void fillSpoilerRects(
QVarLengthArray<QRect, kSpoilersRectsSize> &rects,
QVarLengthArray<FixedRange> &ranges);
void paintSpoilerRects();
void paintSpoilerRects(
const QVarLengthArray<QRect, kSpoilersRectsSize> &rects,
const style::color &color,
int index);
void elideSaveBlock( void elideSaveBlock(
int32 blockIndex, int32 blockIndex,
const AbstractBlock *&_endBlock, const AbstractBlock *&_endBlock,
@ -100,9 +123,8 @@ private:
const QPen *_currentPen = nullptr; const QPen *_currentPen = nullptr;
const QPen *_currentPenSelected = nullptr; const QPen *_currentPenSelected = nullptr;
struct { struct {
const style::color *spoiler = nullptr;
const style::color *spoilerSelected = nullptr;
bool inFront = false; bool inFront = false;
bool spoiler = true;
crl::time startMs = 0; crl::time startMs = 0;
uint16 spoilerIndex = 0; uint16 spoilerIndex = 0;
@ -115,6 +137,10 @@ private:
bool _fullWidthSelection = true; bool _fullWidthSelection = true;
const QChar *_str = nullptr; const QChar *_str = nullptr;
mutable crl::time _cachedNow = 0; mutable crl::time _cachedNow = 0;
QVarLengthArray<FixedRange> _spoilerRanges;
QVarLengthArray<FixedRange> _spoilerSelectedRanges;
QVarLengthArray<QRect, kSpoilersRectsSize> _spoilerRects;
QVarLengthArray<QRect, kSpoilersRectsSize> _spoilerSelectedRects;
int _customEmojiSize = 0; int _customEmojiSize = 0;
int _customEmojiSkip = 0; int _customEmojiSkip = 0;
@ -122,7 +148,7 @@ private:
// current paragraph data // current paragraph data
String::TextBlocks::const_iterator _parStartBlock; String::TextBlocks::const_iterator _parStartBlock;
Qt::LayoutDirection _parDirection; Qt::LayoutDirection _parDirection = Qt::LayoutDirectionAuto;
int _parStart = 0; int _parStart = 0;
int _parLength = 0; int _parLength = 0;
bool _parHasBidi = false; bool _parHasBidi = false;