Optimize spoiler revealing.

This commit is contained in:
John Preston 2022-09-18 16:51:29 +04:00
parent a60fe582ad
commit bc76e4f601
11 changed files with 93 additions and 178 deletions

View file

@ -271,8 +271,6 @@ PRIVATE
ui/rect_part.h
ui/round_rect.cpp
ui/round_rect.h
ui/spoiler_click_handler.cpp
ui/spoiler_click_handler.h
ui/rp_widget.cpp
ui/rp_widget.h
ui/ui_utility.cpp

View file

@ -775,6 +775,10 @@ int SpoilerAnimation::index(crl::time now, bool paused) {
return absolute % kDefaultFramesCount;
}
Fn<void()> SpoilerAnimation::repaintCallback() const {
return _repaint;
}
bool SpoilerAnimation::repaint(crl::time now) {
if (!_scheduled) {
_scheduled = true;

View file

@ -96,7 +96,9 @@ public:
explicit SpoilerAnimation(Fn<void()> repaint);
~SpoilerAnimation();
int index(crl::time now, bool paused);
[[nodiscard]] int index(crl::time now, bool paused);
[[nodiscard]] Fn<void()> repaintCallback() const;
private:
friend class SpoilerAnimationManager;

View file

@ -1,40 +0,0 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/spoiler_click_handler.h"
#include "ui/effects/animation_value.h"
#include "ui/text/text_entity.h"
ClickHandler::TextEntity SpoilerClickHandler::getTextEntity() const {
return { EntityType::Spoiler };
}
void SpoilerClickHandler::onClick(ClickContext context) const {
if (!_shown) {
const auto nonconst = const_cast<SpoilerClickHandler*>(this);
nonconst->_shown = true;
}
}
bool SpoilerClickHandler::shown() const {
return _shown;
}
crl::time SpoilerClickHandler::startMs() const {
return _startMs;
}
void SpoilerClickHandler::setStartMs(crl::time value) {
if (anim::Disabled()) {
return;
}
_startMs = value;
}
void SpoilerClickHandler::setShown(bool value) {
_shown = value;
}

View file

@ -1,30 +0,0 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "ui/click_handler.h"
enum class EntityType : uchar;
class SpoilerClickHandler : public ClickHandler {
public:
SpoilerClickHandler() = default;
TextEntity getTextEntity() const override;
void onClick(ClickContext context) const override;
[[nodiscard]] bool shown() const;
void setShown(bool value);
[[nodiscard]] crl::time startMs() const;
void setStartMs(crl::time value);
private:
bool _shown = false;
crl::time _startMs = 0;
};

View file

@ -12,7 +12,6 @@
#include "ui/text/text_spoiler_data.h"
#include "ui/basic_click_handlers.h"
#include "ui/painter.h"
#include "ui/spoiler_click_handler.h"
#include "base/platform/base_platform_info.h"
#include "styles/style_basic.h"
@ -318,31 +317,40 @@ void String::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) {
_links[lnkIndex - 1] = lnk;
}
void String::setSpoiler(
uint16 lnkIndex,
const std::shared_ptr<SpoilerClickHandler> &lnk) {
if (!lnkIndex || !_spoiler || lnkIndex > _spoiler->links.size()) {
void String::setSpoilerRevealed(bool revealed, anim::type animated) {
if (!_spoiler) {
return;
} else if (_spoiler->revealed == revealed) {
if (animated == anim::type::instant
&& _spoiler->revealAnimation.animating()) {
_spoiler->revealAnimation.stop();
_spoiler->animation.repaintCallback()();
}
return;
}
_spoiler->links[lnkIndex - 1] = lnk;
_spoiler->revealed = revealed;
if (animated == anim::type::instant) {
_spoiler->revealAnimation.stop();
_spoiler->animation.repaintCallback()();
} else {
_spoiler->revealAnimation.start(
_spoiler->animation.repaintCallback(),
revealed ? 0. : 1.,
revealed ? 1. : 0.,
st::fadeWrapDuration);
}
}
void String::setSpoilerShown(uint16 lnkIndex, bool shown) {
if (!lnkIndex
|| !_spoiler
|| (lnkIndex > _spoiler->links.size())
|| !_spoiler->links[lnkIndex - 1]) {
return;
}
_spoiler->links[lnkIndex - 1]->setShown(shown);
void String::setSpoilerLink(const ClickHandlerPtr &lnk) {
_spoiler->link = lnk;
}
bool String::hasLinks() const {
return !_links.isEmpty();
}
int String::spoilersCount() const {
return !_spoiler ? 0 : int(_spoiler->links.size());
bool String::hasSpoilers() const {
return (_spoiler != nullptr);
}
bool String::hasSkipBlock() const {
@ -752,9 +760,11 @@ void String::enumerateText(
if (rangeTo > rangeFrom) { // handle click handler
const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom);
// Ignore links that are partially copied.
const auto handler = (spoilerFrom != rangeFrom || blockFrom != rangeTo || !_spoiler)
const auto handler = (spoilerFrom != rangeFrom
|| blockFrom != rangeTo
|| !_spoiler)
? nullptr
: _spoiler->links.at(spoilerIndex - 1);
: _spoiler->link;
const auto type = EntityType::Spoiler;
clickHandlerFinishCallback(r, handler, type);
}
@ -1040,14 +1050,6 @@ void String::clearFields() {
_startDir = Qt::LayoutDirectionAuto;
}
ClickHandlerPtr String::spoilerLink(uint16 spoilerIndex) const {
if (spoilerIndex && _spoiler) {
const auto &handler = _spoiler->links.at(spoilerIndex - 1);
return (handler && !handler->shown()) ? handler : nullptr;
}
return nullptr;
}
bool IsBad(QChar ch) {
return (ch == 0)
|| (ch >= 8232 && ch < 8237)

View file

@ -16,7 +16,10 @@
#include <any>
class Painter;
class SpoilerClickHandler;
namespace anim {
enum class type : uchar;
} // namespace anim
namespace style {
struct TextPalette;
@ -162,13 +165,12 @@ public:
void setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options = kDefaultTextOptions);
void setMarkedText(const style::TextStyle &st, const TextWithEntities &textWithEntities, const TextParseOptions &options = kMarkupTextOptions, const std::any &context = {});
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
[[nodiscard]] bool hasLinks() const;
void setSpoiler(
uint16 lnkIndex,
const std::shared_ptr<SpoilerClickHandler> &lnk);
void setSpoilerShown(uint16 lnkIndex, bool shown);
[[nodiscard]] int spoilersCount() const;
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
[[nodiscard]] bool hasSpoilers() const;
void setSpoilerRevealed(bool revealed, anim::type animated);
void setSpoilerLink(const ClickHandlerPtr &lnk);
[[nodiscard]] bool hasSkipBlock() const;
bool updateSkipBlock(int width, int height);
@ -254,8 +256,6 @@ private:
// it is also called from move constructor / assignment operator
void clearFields();
[[nodiscard]] ClickHandlerPtr spoilerLink(uint16 spoilerIndex) const;
TextForMimeData toText(
TextSelection selection,
bool composeExpanded,

View file

@ -10,7 +10,6 @@
#include "ui/integration.h"
#include "ui/text/text_isolated_emoji.h"
#include "ui/text/text_spoiler_data.h"
#include "ui/spoiler_click_handler.h"
#include "styles/style_basic.h"
#include <QtCore/QUrl>
@ -634,19 +633,11 @@ void Parser::finalize(const TextParseOptions &options) {
_t->_isIsolatedEmoji = false;
}
}
const auto spoilerIndex = block->spoilerIndex();
if (spoilerIndex) {
if (block->spoilerIndex()) {
if (!_t->_spoiler) {
_t->_spoiler = std::make_unique<SpoilerData>(
Integration::Instance().createSpoilerRepaint(_context));
}
if (_t->_spoiler->links.size() < spoilerIndex) {
_t->_spoiler->links.resize(spoilerIndex);
const auto handler = (options.flags & TextParseLinks)
? std::make_shared<SpoilerClickHandler>()
: nullptr;
_t->setSpoiler(spoilerIndex, std::move(handler));
}
}
const auto shiftedIndex = block->lnkIndex();
auto useCustomIndex = false;
@ -712,9 +703,6 @@ void Parser::finalize(const TextParseOptions &options) {
_t->_isIsolatedEmoji = false;
}
_t->_links.squeeze();
if (_t->_spoiler) {
_t->_spoiler->links.squeeze();
}
_t->_blocks.shrink_to_fit();
_t->_text.squeeze();
}

View file

@ -7,7 +7,6 @@
#include "ui/text/text_renderer.h"
#include "ui/text/text_spoiler_data.h"
#include "ui/spoiler_click_handler.h"
#include "styles/style_basic.h"
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
@ -200,6 +199,10 @@ void Renderer::draw(QPainter &p, const PaintContext &context) {
_align = context.align;
_cachedNow = context.now;
_paused = context.paused;
_spoilerOpacity = _t->_spoiler
? (1. - _t->_spoiler->revealAnimation.value(
_t->_spoiler->revealed ? 1. : 0.))
: 0.;
enumerate();
}
@ -720,12 +723,8 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
if (!_p && _lookupX >= x && _lookupX < x + si.width) { // _lookupRequest
if (_lookupLink) {
if (_lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) {
const auto spoilerLink = _t->spoilerLink(currentBlock->spoilerIndex());
const auto resultLink = (spoilerLink || !currentBlock->lnkIndex())
? spoilerLink
: _t->_links.at(currentBlock->lnkIndex() - 1);
if (resultLink) {
_lookupResult.link = resultLink;
if (const auto link = lookupLink(currentBlock)) {
_lookupResult.link = link;
}
}
}
@ -802,19 +801,16 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
}
}
}
const auto hasSpoiler = _background.inFront
|| _background.startMs;
const auto hasSpoiler = _background.spoiler
&& (_spoilerOpacity > 0.);
if (hasSpoiler) {
fillSpoiler = { x, x + si.width };
}
const auto spoilerOpacity = hasSpoiler
? fillSpoilerOpacity()
: 0.;
fillSelectRange(fillSelect);
const auto opacity = _p->opacity();
if (spoilerOpacity < 1.) {
if (!hasSpoiler || _spoilerOpacity < 1.) {
if (hasSpoiler) {
_p->setOpacity(opacity * (1. - spoilerOpacity));
_p->setOpacity(opacity * (1. - _spoilerOpacity));
}
const auto x = (glyphX + st::emojiPadding).toInt();
const auto y = _y + _yDelta + emojiY;
@ -872,12 +868,8 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
if (!_p && _lookupX >= x && _lookupX < x + itemWidth) { // _lookupRequest
if (_lookupLink) {
if (_lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) {
const auto spoilerLink = _t->spoilerLink(currentBlock->spoilerIndex());
const auto resultLink = (spoilerLink || !currentBlock->lnkIndex())
? spoilerLink
: _t->_links.at(currentBlock->lnkIndex() - 1);
if (resultLink) {
_lookupResult.link = resultLink;
if (const auto link = lookupLink(currentBlock)) {
_lookupResult.link = link;
}
}
}
@ -987,17 +979,15 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
fillSelect = { selX, selX + selWidth };
fillSelectRange(fillSelect);
}
const auto hasSpoiler = (_background.inFront || _background.startMs);
const auto spoilerOpacity = hasSpoiler
? fillSpoilerOpacity()
: 0.;
const auto hasSpoiler = _background.spoiler
&& (_spoilerOpacity > 0.);
const auto opacity = _p->opacity();
const auto isElidedBlock = !rtl
&& (_indexOfElidedBlock == blockIndex);
const auto complexClipping = hasSpoiler
&& isElidedBlock
&& spoilerOpacity == 1.;
if ((spoilerOpacity < 1.) || isElidedBlock) {
&& (_spoilerOpacity == 1.);
if (!hasSpoiler || (_spoilerOpacity < 1.) || isElidedBlock) {
const auto complexClippingEnabled = complexClipping
&& _p->hasClipping();
const auto complexClippingRegion = complexClipping
@ -1015,7 +1005,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
_y + 2 * _lineHeight),
Qt::IntersectClip);
} else if (hasSpoiler && !isElidedBlock) {
_p->setOpacity(opacity * (1. - spoilerOpacity));
_p->setOpacity(opacity * (1. - _spoilerOpacity));
}
if (Q_UNLIKELY(hasSelected)) {
if (Q_UNLIKELY(hasNotSelected)) {
@ -1091,22 +1081,6 @@ void Renderer::fillSelectRange(FixedRange range) {
_p->fillRect(left, _y + _yDelta, width, _fontHeight, _palette->selectBg);
}
float64 Renderer::fillSpoilerOpacity() {
if (!_background.startMs) {
return 1.;
}
const auto progress = float64(now() - _background.startMs)
/ st::fadeWrapDuration;
if ((progress > 1.) && _background.spoilerIndex && _t->_spoiler) {
const auto link = _t->_spoiler->links.at(
_background.spoilerIndex - 1);
if (link) {
link->setStartMs(0);
}
}
return (1. - std::min(progress, 1.));
}
void Renderer::pushSpoilerRange(
FixedRange range,
FixedRange selected,
@ -1159,9 +1133,15 @@ void Renderer::fillSpoilerRects(
}
void Renderer::paintSpoilerRects() {
Expects(_p != nullptr);
if (!_t->_spoiler) {
return;
}
const auto opacity = _p->opacity();
if (_spoilerOpacity < 1.) {
_p->setOpacity(opacity * _spoilerOpacity);
}
const auto index = _t->_spoiler->animation.index(now(), _paused);
paintSpoilerRects(
_spoilerRects,
@ -1171,6 +1151,9 @@ void Renderer::paintSpoilerRects() {
_spoilerSelectedRects,
_palette->selectSpoilerFg,
index);
if (_spoilerOpacity < 1.) {
_p->setOpacity(opacity);
}
}
void Renderer::paintSpoilerRects(
@ -2009,15 +1992,11 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) {
const auto isMono = IsMono(block->flags());
_background = {};
if (block->spoilerIndex() && _t->_spoiler) {
const auto handler
= _t->_spoiler->links.at(block->spoilerIndex() - 1);
const auto inBack = (handler && handler->shown());
_background.inFront = !inBack;
_background.spoiler = true;
_background.startMs = handler ? handler->startMs() : 0;
_background.spoilerIndex = block->spoilerIndex();
}
if (isMono && block->lnkIndex() && !_background.inFront) {
if (isMono
&& block->lnkIndex()
&& (!_background.spoiler || _t->_spoiler->revealed)) {
_background.selectActiveBlock = ClickHandler::showAsPressed(
_t->_links.at(block->lnkIndex() - 1));
}
@ -2036,4 +2015,15 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) {
}
}
ClickHandlerPtr Renderer::lookupLink(const AbstractBlock *block) const {
const auto spoilerLink = (_t->_spoiler
&& !_t->_spoiler->revealed
&& block->spoilerIndex())
? _t->_spoiler->link
: ClickHandlerPtr();
return (spoilerLink || !block->lnkIndex())
? spoilerLink
: _t->_links.at(block->lnkIndex() - 1);
}
} // namespace Ui::Text

View file

@ -59,7 +59,6 @@ private:
const String::TextBlocks::const_iterator &_endBlockIter,
const String::TextBlocks::const_iterator &_end);
void fillSelectRange(FixedRange range);
[[nodiscard]] float64 fillSpoilerOpacity();
void pushSpoilerRange(
FixedRange range,
FixedRange selected,
@ -108,6 +107,8 @@ private:
bool eBidiItemize(QScriptAnalysis *analysis, BidiControl &control);
void applyBlockProperties(const AbstractBlock *block);
[[nodiscard]] ClickHandlerPtr lookupLink(
const AbstractBlock *block) const;
const String *_t = nullptr;
SpoilerMessCache *_spoilerCache = nullptr;
@ -123,11 +124,7 @@ private:
const QPen *_currentPen = nullptr;
const QPen *_currentPenSelected = nullptr;
struct {
bool inFront = false;
bool spoiler = true;
crl::time startMs = 0;
uint16 spoilerIndex = 0;
bool spoiler = false;
bool selectActiveBlock = false; // For monospace.
} _background;
int _yFrom = 0;
@ -137,6 +134,7 @@ private:
bool _fullWidthSelection = true;
const QChar *_str = nullptr;
mutable crl::time _cachedNow = 0;
float64 _spoilerOpacity = 0.;
QVarLengthArray<FixedRange> _spoilerRanges;
QVarLengthArray<FixedRange> _spoilerSelectedRanges;
QVarLengthArray<QRect, kSpoilersRectsSize> _spoilerRects;

View file

@ -7,6 +7,7 @@
#pragma once
#include "ui/effects/spoiler_mess.h"
#include "ui/effects/animations.h"
class SpoilerClickHandler;
@ -18,7 +19,9 @@ struct SpoilerData {
}
SpoilerAnimation animation;
QVector<std::shared_ptr<SpoilerClickHandler>> links;
ClickHandlerPtr link;
Animations::Simple revealAnimation;
bool revealed = false;
};
} // namespace Ui::Text