From bc76e4f601eb68ecff8f1d6442b9a57171ce0c05 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 18 Sep 2022 16:51:29 +0400 Subject: [PATCH] Optimize spoiler revealing. --- CMakeLists.txt | 2 - ui/effects/spoiler_mess.cpp | 4 ++ ui/effects/spoiler_mess.h | 4 +- ui/spoiler_click_handler.cpp | 40 ---------------- ui/spoiler_click_handler.h | 30 ------------ ui/text/text.cpp | 54 +++++++++++----------- ui/text/text.h | 18 ++++---- ui/text/text_parser.cpp | 14 +----- ui/text/text_renderer.cpp | 90 ++++++++++++++++-------------------- ui/text/text_renderer.h | 10 ++-- ui/text/text_spoiler_data.h | 5 +- 11 files changed, 93 insertions(+), 178 deletions(-) delete mode 100644 ui/spoiler_click_handler.cpp delete mode 100644 ui/spoiler_click_handler.h diff --git a/CMakeLists.txt b/CMakeLists.txt index efd551e..084ca64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/ui/effects/spoiler_mess.cpp b/ui/effects/spoiler_mess.cpp index a125bd5..b07f8ac 100644 --- a/ui/effects/spoiler_mess.cpp +++ b/ui/effects/spoiler_mess.cpp @@ -775,6 +775,10 @@ int SpoilerAnimation::index(crl::time now, bool paused) { return absolute % kDefaultFramesCount; } +Fn SpoilerAnimation::repaintCallback() const { + return _repaint; +} + bool SpoilerAnimation::repaint(crl::time now) { if (!_scheduled) { _scheduled = true; diff --git a/ui/effects/spoiler_mess.h b/ui/effects/spoiler_mess.h index 0ca429f..1cf9102 100644 --- a/ui/effects/spoiler_mess.h +++ b/ui/effects/spoiler_mess.h @@ -96,7 +96,9 @@ public: explicit SpoilerAnimation(Fn repaint); ~SpoilerAnimation(); - int index(crl::time now, bool paused); + [[nodiscard]] int index(crl::time now, bool paused); + + [[nodiscard]] Fn repaintCallback() const; private: friend class SpoilerAnimationManager; diff --git a/ui/spoiler_click_handler.cpp b/ui/spoiler_click_handler.cpp deleted file mode 100644 index ccb16eb..0000000 --- a/ui/spoiler_click_handler.cpp +++ /dev/null @@ -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(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; -} diff --git a/ui/spoiler_click_handler.h b/ui/spoiler_click_handler.h deleted file mode 100644 index bacaf68..0000000 --- a/ui/spoiler_click_handler.h +++ /dev/null @@ -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; - -}; diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 6dd1d8d..1991f70 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -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 &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) diff --git a/ui/text/text.h b/ui/text/text.h index 090a39c..d99a32f 100644 --- a/ui/text/text.h +++ b/ui/text/text.h @@ -16,7 +16,10 @@ #include 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 &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, diff --git a/ui/text/text_parser.cpp b/ui/text/text_parser.cpp index 0db97da..a435fe6 100644 --- a/ui/text/text_parser.cpp +++ b/ui/text/text_parser.cpp @@ -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 @@ -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( Integration::Instance().createSpoilerRepaint(_context)); } - if (_t->_spoiler->links.size() < spoilerIndex) { - _t->_spoiler->links.resize(spoilerIndex); - const auto handler = (options.flags & TextParseLinks) - ? std::make_shared() - : 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(); } diff --git a/ui/text/text_renderer.cpp b/ui/text/text_renderer.cpp index 7b5033b..53e5648 100644 --- a/ui/text/text_renderer.cpp +++ b/ui/text/text_renderer.cpp @@ -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 diff --git a/ui/text/text_renderer.h b/ui/text/text_renderer.h index e904c14..6bf4a6b 100644 --- a/ui/text/text_renderer.h +++ b/ui/text/text_renderer.h @@ -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 _spoilerRanges; QVarLengthArray _spoilerSelectedRanges; QVarLengthArray _spoilerRects; diff --git a/ui/text/text_spoiler_data.h b/ui/text/text_spoiler_data.h index aae50f6..4b3943a 100644 --- a/ui/text/text_spoiler_data.h +++ b/ui/text/text_spoiler_data.h @@ -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> links; + ClickHandlerPtr link; + Animations::Simple revealAnimation; + bool revealed = false; }; } // namespace Ui::Text