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) {
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;
}

View file

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

View file

@ -61,8 +61,31 @@ void InitTextItemWithScriptItem(QTextItemInt &ti, const QScriptItem &si) {
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 {
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<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;
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<QRect, kSpoilersRectsSize> &rects,
QVarLengthArray<FixedRange> &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<QRect, kSpoilersRectsSize> &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();
}

View file

@ -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<QRect, kSpoilersRectsSize> &rects,
QVarLengthArray<FixedRange> &ranges);
void paintSpoilerRects();
void paintSpoilerRects(
const QVarLengthArray<QRect, kSpoilersRectsSize> &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<FixedRange> _spoilerRanges;
QVarLengthArray<FixedRange> _spoilerSelectedRanges;
QVarLengthArray<QRect, kSpoilersRectsSize> _spoilerRects;
QVarLengthArray<QRect, kSpoilersRectsSize> _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;