diff --git a/CMakeLists.txt b/CMakeLists.txt index 084ca64..d94a186 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,6 +166,7 @@ PRIVATE ui/text/text_parser.h ui/text/text_renderer.cpp ui/text/text_renderer.h + ui/text/text_spoiler_data.cpp ui/text/text_spoiler_data.h ui/text/text_utilities.cpp ui/text/text_utilities.h diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 24185b1..9fd9a8e 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -125,6 +125,30 @@ not_null DefaultSpoilerCache() { return &data.cache; } +String::SpoilerDataWrap::SpoilerDataWrap() noexcept = default; + +String::SpoilerDataWrap::SpoilerDataWrap(SpoilerDataWrap &&other) noexcept +: data(std::move(other.data)) { + adjustFrom(&other); +} + +String::SpoilerDataWrap &String::SpoilerDataWrap::operator=( + SpoilerDataWrap &&other) noexcept { + data = std::move(other.data); + adjustFrom(&other); + return *this; +} + +void String::SpoilerDataWrap::adjustFrom(const SpoilerDataWrap *other) { + if (data && data->link) { + const auto raw = [](auto pointer) { + return reinterpret_cast(pointer); + }; + data->link->setText(reinterpret_cast( + raw(data->link->text().get()) + raw(this) - raw(other))); + } +} + String::String(int32 minResizeWidth) : _minResizeWidth(minResizeWidth) { } @@ -318,31 +342,36 @@ void String::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) { } void String::setSpoilerRevealed(bool revealed, anim::type animated) { - if (!_spoiler) { + const auto data = _spoiler.data.get(); + if (!data) { return; - } else if (_spoiler->revealed == revealed) { + } else if (data->revealed == revealed) { if (animated == anim::type::instant - && _spoiler->revealAnimation.animating()) { - _spoiler->revealAnimation.stop(); - _spoiler->animation.repaintCallback()(); + && data->revealAnimation.animating()) { + data->revealAnimation.stop(); + data->animation.repaintCallback()(); } return; } - _spoiler->revealed = revealed; + data->revealed = revealed; if (animated == anim::type::instant) { - _spoiler->revealAnimation.stop(); - _spoiler->animation.repaintCallback()(); + data->revealAnimation.stop(); + data->animation.repaintCallback()(); } else { - _spoiler->revealAnimation.start( - _spoiler->animation.repaintCallback(), + data->revealAnimation.start( + data->animation.repaintCallback(), revealed ? 0. : 1., revealed ? 1. : 0., st::fadeWrapDuration); } } -void String::setSpoilerLink(const ClickHandlerPtr &lnk) { - _spoiler->link = lnk; +void String::setSpoilerLinkFilter(Fn filter) { + Expects(_spoiler.data != nullptr); + + _spoiler.data->link = std::make_shared( + this, + std::move(filter)); } bool String::hasLinks() const { @@ -350,7 +379,7 @@ bool String::hasLinks() const { } bool String::hasSpoilers() const { - return (_spoiler != nullptr); + return (_spoiler.data != nullptr); } bool String::hasSkipBlock() const { @@ -762,9 +791,9 @@ void String::enumerateText( // Ignore links that are partially copied. const auto handler = (spoilerFrom != rangeFrom || blockFrom != rangeTo - || !_spoiler) + || !_spoiler.data) ? nullptr - : _spoiler->link; + : _spoiler.data->link; const auto type = EntityType::Spoiler; clickHandlerFinishCallback(r, handler, type); } @@ -805,7 +834,7 @@ void String::enumerateText( } bool String::hasPersistentAnimation() const { - return _hasCustomEmoji || _spoiler; + return _hasCustomEmoji || _spoiler.data; } void String::unloadPersistentAnimation() { @@ -1018,7 +1047,7 @@ IsolatedEmoji String::toIsolatedEmoji() const { auto result = IsolatedEmoji(); const auto skip = (_blocks.empty() || _blocks.back()->type() != TextBlockTSkip) ? 0 : 1; - if ((_blocks.size() > kIsolatedEmojiLimit + skip) || _spoiler) { + if ((_blocks.size() > kIsolatedEmojiLimit + skip) || hasSpoilers()) { return {}; } auto index = 0; @@ -1046,7 +1075,7 @@ void String::clear() { void String::clearFields() { _blocks.clear(); _links.clear(); - _spoiler = nullptr; + _spoiler.data = nullptr; _maxWidth = _minHeight = 0; _startDir = Qt::LayoutDirectionAuto; } diff --git a/ui/text/text.h b/ui/text/text.h index d99a32f..40be2b5 100644 --- a/ui/text/text.h +++ b/ui/text/text.h @@ -170,7 +170,7 @@ public: [[nodiscard]] bool hasSpoilers() const; void setSpoilerRevealed(bool revealed, anim::type animated); - void setSpoilerLink(const ClickHandlerPtr &lnk); + void setSpoilerLinkFilter(Fn filter); [[nodiscard]] bool hasSkipBlock() const; bool updateSkipBlock(int width, int height); @@ -237,6 +237,19 @@ private: using TextBlocks = std::vector; using TextLinks = QVector; + class SpoilerDataWrap { + public: + SpoilerDataWrap() noexcept; + SpoilerDataWrap(SpoilerDataWrap &&other) noexcept; + SpoilerDataWrap &operator=(SpoilerDataWrap &&other) noexcept; + + std::unique_ptr data; + + private: + void adjustFrom(const SpoilerDataWrap *other); + + }; + uint16 countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const; uint16 countBlockLength(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const; @@ -276,7 +289,7 @@ private: Qt::LayoutDirection _startDir = Qt::LayoutDirectionAuto; - std::unique_ptr _spoiler; + SpoilerDataWrap _spoiler; friend class Parser; friend class Renderer; diff --git a/ui/text/text_parser.cpp b/ui/text/text_parser.cpp index a435fe6..b558054 100644 --- a/ui/text/text_parser.cpp +++ b/ui/text/text_parser.cpp @@ -207,14 +207,6 @@ void Parser::createBlock(int32 skipBack) { } } -// Unused. -// void Parser::createSkipBlock(int32 w, int32 h) { -// createBlock(); -// _t->_text.push_back('_'); -// _t->_blocks.push_back(Block::Skip(_t->_st->font, _t->_text, _blockStart++, w, h, _monoIndex ? _monoIndex : _lnkIndex, _spoilerIndex)); -// blockCreated(); -// } - void Parser::createNewlineBlock() { createBlock(); _t->_text.push_back(QChar::LineFeed); @@ -634,8 +626,8 @@ void Parser::finalize(const TextParseOptions &options) { } } if (block->spoilerIndex()) { - if (!_t->_spoiler) { - _t->_spoiler = std::make_unique( + if (!_t->_spoiler.data) { + _t->_spoiler.data = std::make_unique( Integration::Instance().createSpoilerRepaint(_context)); } } @@ -696,10 +688,10 @@ void Parser::finalize(const TextParseOptions &options) { } lastHandlerIndex.lnk = realIndex; } - if (!_t->_hasCustomEmoji || _t->_spoiler) { + if (!_t->_hasCustomEmoji || _t->_spoiler.data) { _t->_isOnlyCustomEmoji = false; } - if (_t->_blocks.empty() || _t->_spoiler) { + if (_t->_blocks.empty() || _t->_spoiler.data) { _t->_isIsolatedEmoji = false; } _t->_links.squeeze(); diff --git a/ui/text/text_parser.h b/ui/text/text_parser.h index f105a1c..e14595a 100644 --- a/ui/text/text_parser.h +++ b/ui/text/text_parser.h @@ -56,7 +56,6 @@ private: void trimSourceRange(); void blockCreated(); void createBlock(int32 skipBack = 0); - // void createSkipBlock(int32 w, int32 h); void createNewlineBlock(); // Returns true if at least one entity was parsed in the current position. diff --git a/ui/text/text_renderer.cpp b/ui/text/text_renderer.cpp index 53e5648..7d1e7a5 100644 --- a/ui/text/text_renderer.cpp +++ b/ui/text/text_renderer.cpp @@ -154,7 +154,8 @@ bool Distinct(FixedRange a, FixedRange b) { } Renderer::Renderer(const Ui::Text::String &t) -: _t(&t) { +: _t(&t) +, _spoiler(_t->_spoiler.data.get()) { } Renderer::~Renderer() { @@ -199,9 +200,9 @@ 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.)) + _spoilerOpacity = _spoiler + ? (1. - _spoiler->revealAnimation.value( + _spoiler->revealed ? 1. : 0.)) : 0.; enumerate(); } @@ -1085,7 +1086,7 @@ void Renderer::pushSpoilerRange( FixedRange range, FixedRange selected, int currentBlockIndex) { - if (!_background.spoiler || !_t->_spoiler) { + if (!_background.spoiler || !_spoiler) { return; } const auto elided = (_indexOfElidedBlock == currentBlockIndex) @@ -1135,14 +1136,14 @@ void Renderer::fillSpoilerRects( void Renderer::paintSpoilerRects() { Expects(_p != nullptr); - if (!_t->_spoiler) { + if (!_spoiler) { return; } const auto opacity = _p->opacity(); if (_spoilerOpacity < 1.) { _p->setOpacity(opacity * _spoilerOpacity); } - const auto index = _t->_spoiler->animation.index(now(), _paused); + const auto index = _spoiler->animation.index(now(), _paused); paintSpoilerRects( _spoilerRects, _palette->spoilerFg, @@ -1991,12 +1992,12 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) { if (_p) { const auto isMono = IsMono(block->flags()); _background = {}; - if (block->spoilerIndex() && _t->_spoiler) { + if (block->spoilerIndex() && _spoiler) { _background.spoiler = true; } if (isMono && block->lnkIndex() - && (!_background.spoiler || _t->_spoiler->revealed)) { + && (!_background.spoiler || _spoiler->revealed)) { _background.selectActiveBlock = ClickHandler::showAsPressed( _t->_links.at(block->lnkIndex() - 1)); } @@ -2016,10 +2017,10 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) { } ClickHandlerPtr Renderer::lookupLink(const AbstractBlock *block) const { - const auto spoilerLink = (_t->_spoiler - && !_t->_spoiler->revealed + const auto spoilerLink = (_spoiler + && !_spoiler->revealed && block->spoilerIndex()) - ? _t->_spoiler->link + ? _spoiler->link : ClickHandlerPtr(); return (spoilerLink || !block->lnkIndex()) ? spoilerLink diff --git a/ui/text/text_renderer.h b/ui/text/text_renderer.h index 6bf4a6b..544ee55 100644 --- a/ui/text/text_renderer.h +++ b/ui/text/text_renderer.h @@ -111,6 +111,7 @@ private: const AbstractBlock *block) const; const String *_t = nullptr; + SpoilerData *_spoiler = nullptr; SpoilerMessCache *_spoilerCache = nullptr; QPainter *_p = nullptr; const style::TextPalette *_palette = nullptr; diff --git a/ui/text/text_spoiler_data.cpp b/ui/text/text_spoiler_data.cpp new file mode 100644 index 0000000..57127bc --- /dev/null +++ b/ui/text/text_spoiler_data.cpp @@ -0,0 +1,36 @@ +// 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/text/text_spoiler_data.h" + +#include "ui/text/text.h" + +namespace Ui::Text { + +SpoilerClickHandler::SpoilerClickHandler( + not_null text, + Fn filter) +: _text(text) +, _filter(std::move(filter)) { +} + +not_null SpoilerClickHandler::text() const { + return _text; +} + +void SpoilerClickHandler::setText(not_null text) { + _text = text; +} + +void SpoilerClickHandler::onClick(ClickContext context) const { + if (_filter && !_filter(context)) { + return; + } + _text->setSpoilerRevealed(true, anim::type::normal); +} + +} // namespace Ui::Text + diff --git a/ui/text/text_spoiler_data.h b/ui/text/text_spoiler_data.h index 4b3943a..5a20926 100644 --- a/ui/text/text_spoiler_data.h +++ b/ui/text/text_spoiler_data.h @@ -8,18 +8,36 @@ #include "ui/effects/spoiler_mess.h" #include "ui/effects/animations.h" - -class SpoilerClickHandler; +#include "ui/click_handler.h" namespace Ui::Text { +class String; + +class SpoilerClickHandler final : public ClickHandler { +public: + SpoilerClickHandler( + not_null text, + Fn filter); + + [[nodiscard]] not_null text() const; + void setText(not_null text); + + void onClick(ClickContext context) const override; + +private: + not_null _text; + const Fn _filter; + +}; + struct SpoilerData { explicit SpoilerData(Fn repaint) : animation(std::move(repaint)) { } SpoilerAnimation animation; - ClickHandlerPtr link; + std::shared_ptr link; Animations::Simple revealAnimation; bool revealed = false; };